构建自己的web框架(二)

一,核心类:该类封装了ServletContext,是一个单例,其主要功能是提供配置文件的加载和解析,url的解析,work组件的查找和调用,资源的获取,释放,提供事件回调接口,处理注解,注册work组件,提供缓冲区,管理web对象等一系列功能,代码很简单,如下:

  1 /**
  2  * 
  3  */
  4 package com.jjh.jee.hawk;
  5 
  6 import java.io.IOException;
  7 import java.io.InputStream;
  8 import java.lang.reflect.Method;
  9 import java.net.MalformedURLException;
 10 import java.net.URI;
 11 import java.net.URISyntaxException;
 12 import java.net.URL;
 13 import java.nio.file.Path;
 14 import java.nio.file.Paths;
 15 import java.util.ArrayList;
 16 import java.util.Collections;
 17 import java.util.HashMap;
 18 import java.util.List;
 19 import java.util.Map;
 20 import java.util.Objects;
 21 import java.util.concurrent.ConcurrentLinkedQueue;
 22 import java.util.concurrent.ForkJoinPool;
 23 import java.util.concurrent.TimeUnit;
 24 
 25 import javax.servlet.AsyncContext;
 26 import javax.servlet.AsyncEvent;
 27 import javax.servlet.AsyncListener;
 28 import javax.servlet.ServletContext;
 29 import com.jjh.common.Files;
 30 import com.jjh.common.JAXB;
 31 import com.jjh.common.StringX;
 32 
 33 /**
 34  * @author Administrator
 35  *
 36  */
 37 public final class HawkContext implements AsyncListener{
 38     /**
 39      * @author Administrator
 40      *
 41      */
 42     private static class AnnotationInfo {
 43         /**
 44                  * @author Administrator
 45                  *
 46                  */
 47         private static class AnnotationMethod {
 48             private final Method method;
 49             private final boolean isMain;
 50             private final boolean isUI;
 51             private final int     async;
 52             /**
 53              * @param method
 54              * @param isMain
 55              * @param isUI
 56              * @param async
 57              */
 58             AnnotationMethod(Method method, boolean isMain, boolean isUI, int async) {
 59                 super();
 60                 this.method = method;
 61                 this.isMain = isMain;
 62                 this.isUI = isUI;
 63                 this.async = async;
 64             }
 65             
 66         }
 67 
 68         private final Class<?> claze;
 69         private final Map<String, AnnotationMethod> methods;
 70 
 71         /**
 72          * @param claze
 73          * @param methods
 74          */
 75         AnnotationInfo(Class<?> claze, Map<String, AnnotationMethod> methods) {
 76             super();
 77             this.claze = claze;
 78             this.methods = methods;
 79         }
 80         
 81         private AnnotationMethod  find(String methodUri)
 82         {
 83             return methods.get(methodUri);
 84         }
 85 
 86     }
 87     public static final int SYNC=Integer.MIN_VALUE;
 88     public static final int TIMEOUT=520;
 89     private static HawkContext context = null;
 90     private final ConcurrentLinkedQueue<AsyncContext> clients =new ConcurrentLinkedQueue<>();
 91     private final Map<String, Object> objectMap = Collections.synchronizedMap(new HashMap<>());
 92     private final Map<String, AnnotationInfo> serviceMap = Collections.synchronizedMap(new HashMap<>());
 93     private final ServletContext application;
 94     private final ForkJoinPool threadPool;
 95     private HawkListener listener;
 96     
 97     /**
 98      * @param application
 99      * @throws Exception
100      */
101     HawkContext(ServletContext application) throws Exception {
102         super();
103         this.application = application;
104         // get config:hawk.xml
105         HawkType hawk = JAXB.loadXml(HawkType.class, getResourceAsStream("WEB-INF/hawk.xml"),
106                 HawkType.class.getResourceAsStream("hawk.xsd"));
107         if (StringX.nonEmpty(hawk.listenerClassName)) {
108             Class<?> c = Class.forName(hawk.listenerClassName);
109             if (!HawkListener.class.isAssignableFrom(c))
110                 throw new Exception("'" + c.getName() + "',not a interface HawkListener implement class.");
111             listener = (HawkListener) c.newInstance();
112         }
113         // scan service package
114         for (ScanPackageType spt : hawk.scanPackageList) {
115             for (String className : Files.scanPackage(spt.packageName)) {
116                 Class<?> claze = Class.forName(className);
117                 if (claze.isAnnotationPresent(Service.class)) {
118                     Service service = claze.getAnnotation(Service.class);
119                     String value = service.value();
120                     // com.jjh.j2ee.abc.com.jjh.j2ee.Test
121                     if (value.isEmpty()) {
122                         value = claze.getName().replaceFirst(hawk.basePackage,"").replaceAll("\\.", "/");
123                     }
124                     if(serviceMap.containsKey(value))
125                         throw new RuntimeException("'" + claze.getName() + "',@Service(value='"+value+"'),value is already exist.");
126                     // handle @Service and @Sign
127                     Method[] methods = claze.getMethods();
128                     if (Objects.nonNull(methods)) {
129                         // key:method url
130                         Map<String,AnnotationInfo.AnnotationMethod> map = new HashMap<>();
131                         //not exist main method.
132                         boolean main=false;
133                         for (Method m : methods) {
134                             if (m.isAnnotationPresent(Work.class)) {
135                                 Work sign = m.getAnnotation(Work.class);
136                                 //main already exist
137                                 if(main&&sign.main())
138                                     throw new Exception("'" + m.getName() + "',duplicate main entrance point.");
139                                 main=sign.main();
140                                 if(main&&!sign.UI())
141                                     throw new Exception("'" + m.getName() + "', main entrance point must is a user interface.");
142                                 String val = sign.value();
143                                 if (val.isEmpty())
144                                     val=sign.mapping();
145                                 if (val.isEmpty())
146                                     val = m.getName();
147                                 map.put(val, new AnnotationInfo.AnnotationMethod(m,sign.main(),sign.UI(),m.isAnnotationPresent(Async.class)?m.getAnnotation(Async.class).value():SYNC));
148                             }
149                         }
150                         if (map.isEmpty())
151                             throw new Exception("'" + className + "',at least lack a use @Work annotated method.");
152                         serviceMap.put(value, new AnnotationInfo(claze, map));
153                         if (Objects.nonNull(listener))
154                         {
155                             List<Method> list=new ArrayList<>(map.size());
156                             for(AnnotationInfo.AnnotationMethod aa:map.values())
157                             {
158                                 list.add(aa.method);
159                             }
160                             listener.onScanning(claze, list.toArray(new Method[0]));
161                         }
162                             
163                     }
164 
165                 }
166 
167             }
168         }
169         threadPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors() * hawk.threadPoolSize);
170         if (Objects.nonNull(listener))
171             listener.onInit(this);
172     }
173 
174     static HawkContext create(ServletContext application) throws Exception {
175         return context = new HawkContext(application);
176     }
177 
178     // like:requestUri="/a/b/c"
179     static String[] parse(String requestUri) {
180         Path path = Paths.get(requestUri);
181         Path classUrl = path.getParent();
182         Path methodUrl = path.getFileName();
183         return new String[] { Objects.nonNull(classUrl)?classUrl.toString().replace("\\", "/"):"", Objects.nonNull(methodUrl)?methodUrl.toString():"" };
184         
185     }
186 
187     // like:serviceUrl="/a/b",methodUri="c"
188     static int isAsync(String serviceUrl,String methodUri)throws Exception
189     {
190         AnnotationInfo info = context.serviceMap.get(serviceUrl);
191         AnnotationInfo.AnnotationMethod aa = info.find(methodUri);
192         if(Objects.isNull(aa))
193             throw new Exception("'"+info.claze.getName()+":"+methodUri+"' not exist.");
194         return aa.async;
195     }
196     // like:serviceUrl="/a/b",methodUrl="c"
197     static boolean valid(String serviceUrl,String methodUri) {
198         if(Objects.nonNull(serviceUrl) && context.serviceMap.containsKey(serviceUrl))
199         {
200             AnnotationInfo.AnnotationMethod aa=context.serviceMap.get(serviceUrl).find(methodUri);
201             return Objects.nonNull(aa)&&aa.isUI;
202         }
203         return  false;
204     }
205     
206     // like:serviceUrl="/a/b"
207     static boolean valid(String serviceUrl) {
208         return Objects.nonNull(serviceUrl) && context.serviceMap.containsKey(serviceUrl);
209     } 
210     
211     // like:serviceFullUrl="service:///a/b/c"
212     public static String work(String serviceFullUrl, Bundle bundle) throws Exception{
213         URI uri = new URI(serviceFullUrl);
214         String path = uri.getPath();
215         String[] array = parse(path);
216         if (valid(array[0])) {
217             String ret =null;
218             AnnotationInfo info = context.serviceMap.get(array[0]);
219             Class<?> claze = info.claze;
220             Object obj = context.objectMap.get(array[0]);
221             if (Objects.isNull(obj)) {
222                 obj = claze.newInstance();
223                 if (claze.isAnnotationPresent(Cache.class))
224                     context.objectMap.put(array[0], obj);
225             }
226             AnnotationInfo.AnnotationMethod aa = info.methods.get(array[1]);
227             if (Objects.nonNull(aa)) {
228                 ret = Objects.nonNull(context.listener) ? context.listener.onBeforeWork(claze,aa.method, bundle) : null;
229                 if (Objects.isNull(ret))
230                 {
231                     try
232                     {
233                         ret = (String) aa.method.invoke(obj, bundle);
234                     }
235                     catch(Exception e)
236                     {
237                         if(Objects.nonNull(context.listener))
238                             return context.listener.onException(bundle, e);
239                         
240                     }
241                     if(Objects.nonNull(context.listener)&&Objects.isNull(ret))
242                         ret=context.listener.onAfterWork(claze,aa.method, bundle);
243                     if(aa.isMain)
244                     {
245                         for(Map.Entry<String, AnnotationInfo.AnnotationMethod> entry:info.methods.entrySet())
246                         {
247                             AnnotationInfo.AnnotationMethod am=entry.getValue();
248                             if(am.isUI&&!am.isMain)
249                             {
250                                 String url=bundle.getRequest().getContextPath()+bundle.getRequest().getServletPath()+array[0]+"/"+entry.getKey();
251                                 if(Objects.nonNull(bundle.getRequest().getAttribute(am.method.getName())))
252                                     throw new IllegalArgumentException("'attribute:"+am.method.getName()+"'already exist in request!");    
253                                 bundle.getRequest().setAttribute(am.method.getName(), bundle.getResponse().encodeURL(url));
254                             }
255                              
256                         }
257                     }
258                 }
259                     
260             }
261             return ret;
262         }
263         return null;
264     }
265     // like:serviceFullUrl="service:///a/b/c"
266     public static String workStream(String serviceFullUrl, Bundle bundle) throws Exception
267     {
268         for(;;)
269         {
270             serviceFullUrl=work(serviceFullUrl,bundle);
271             if(Objects.isNull(serviceFullUrl))
272                 return null;
273             URI u=null;
274             try {
275                 u = new URI(serviceFullUrl);
276             } catch (URISyntaxException e) {
277                 // TODO Auto-generated catch block
278                 return serviceFullUrl;
279             }
280             if(StringX.isEmpty(u.getScheme()))
281                 return serviceFullUrl;
282             if(Scheme.valueOf(u.getScheme().toLowerCase().trim())!=Scheme.work)
283                 return serviceFullUrl;
284         }
285     }
286     
287     public void cache(String key,Object value)
288     {
289         application.setAttribute(key, value);
290     }
291     
292     public  void remove(String key)
293     {
294         application.removeAttribute(key);
295     }
296     
297     @SuppressWarnings("unchecked")
298     public <T>T getService(String key)
299     {
300         return (T)objectMap.get(key);
301     }
302     
303     @SuppressWarnings("unchecked")
304     public <T>T get(String key)
305     {
306         return (T)application.getAttribute(key);
307     }
308     
309      /**
310      * @return the application
311      */
312     public ServletContext getServletContext() {
313         return application;
314     }
315 
316     void destroy() {
317         threadPool.shutdownNow();
318         try {
319             threadPool.awaitTermination(3, TimeUnit.SECONDS);
320         } catch (InterruptedException e) {
321             // TODO Auto-generated catch block
322             //e.printStackTrace();
323         }
324         clients.clear();
325         objectMap.clear();
326         serviceMap.clear();
327         context = null;
328         if (Objects.nonNull(listener))
329             listener.onDestroy(this);
330     }
331     
332     
333     void addClient(AsyncContext client)
334     {
335         clients.add(client);
336     }
337 
338     
339     /**
340      * @return the threadPool
341      */
342     ForkJoinPool getThreadPool() {
343         return threadPool;
344     }
345 
346     /**
347      * @return the context
348      */
349     public static HawkContext getContext() {
350         return context;
351     }
352 
353      InputStream getResourceAsStream(String path) {
354         return application.getResourceAsStream(path);
355     }
356 
357      URL getResource(String path) throws MalformedURLException {
358         return application.getResource(path);
359     }
360      
361     public String getContextPath()
362     {
363         return context.application.getRealPath("/");
364     }
365 
366     @Override
367     public void onComplete(AsyncEvent arg0) throws IOException {
368         // TODO Auto-generated method stub
369         clients.remove(arg0.getAsyncContext());
370     }
371 
372     @Override
373     public void onError(AsyncEvent arg0) throws IOException {
374         // TODO Auto-generated method stub
375         arg0.getAsyncContext().complete();
376         clients.remove(arg0.getAsyncContext());
377         arg0.getThrowable().printStackTrace();
378     }
379 
380     @Override
381     public void onStartAsync(AsyncEvent arg0) throws IOException {
382         // TODO Auto-generated method stub
383         
384     }
385 
386     @Override
387     public void onTimeout(AsyncEvent arg0) throws IOException {
388         // TODO Auto-generated method stub
389         arg0.getAsyncContext().complete();
390         clients.remove(arg0.getAsyncContext());
391     }
392 
393 }

二,全局控制器,一个servlet,类似于springmvc的DispatcherServlet,负责处理work返回的结果,向用户发送响应。代码如下:

  1 package com.jjh.jee.hawk;
  2 
  3 import java.io.IOException;
  4 import java.io.OutputStream;
  5 import java.net.URI;
  6 import java.util.Objects;
  7 import java.util.concurrent.RecursiveAction;
  8 
  9 import javax.servlet.AsyncContext;
 10 import javax.servlet.ServletConfig;
 11 import javax.servlet.ServletException;
 12 import javax.servlet.annotation.MultipartConfig;
 13 import javax.servlet.annotation.WebServlet;
 14 import javax.servlet.http.HttpServlet;
 15 import javax.servlet.http.HttpServletRequest;
 16 import javax.servlet.http.HttpServletResponse;
 17 
 18 import com.jjh.common.StringX;
 19 
 20 /**
 21  * Servlet implementation class GlobalController
 22  */
 23 @WebServlet(asyncSupported = true, loadOnStartup = 1, urlPatterns = GlobalController.URL_PREFIX
 24         + "/*", name = "GlobalController")
 25 @MultipartConfig
 26 public class GlobalController extends HttpServlet {
 27     private static final long serialVersionUID = 1L;
 28     public static final String URL_PREFIX = "/hawk";
 29 
 30     /**
 31      * @see HttpServlet#HttpServlet()
 32      */
 33     public GlobalController() {
 34         super();
 35         // TODO Auto-generated constructor stub
 36 
 37     }
 38 
 39     /**
 40      * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
 41      *      response)
 42      */
 43     @Override
 44     protected void doGet(HttpServletRequest request, HttpServletResponse response)
 45             throws ServletException, IOException {
 46         // TODO Auto-generated method stub
 47         request.setCharacterEncoding("UTF-8");
 48         response.setCharacterEncoding("UTF-8");
 49         String uri = request.getRequestURI().replaceFirst(request.getContextPath() + URL_PREFIX, "");
 50         int index = uri.indexOf(';');
 51         String[] urls = HawkContext.parse(uri = uri.substring(0, index < 0 ? uri.length() : index));
 52         String serviceUrl = urls[0];
 53         String methodUrl = urls[1];
 54         if (HawkContext.valid(serviceUrl, methodUrl)) {
 55             Bundle bundle = new Bundle();
 56             int timeout;
 57             try {
 58                 timeout = HawkContext.isAsync(serviceUrl, methodUrl);
 59             } catch (Exception e) {
 60                 response.sendError(404, e.getMessage());
 61                 return;
 62             }
 63             // sync request
 64             if (timeout == HawkContext.SYNC) {
 65                 bundle.setResponse(response);
 66                 bundle.setRequest(request);
 67                 handleService(uri, bundle, request, response);
 68                 bundle.clear();
 69             }
 70             // async request
 71             else if (timeout > 0) {
 72                 if (request.isAsyncStarted()) {
 73                     request.getAsyncContext().complete();
 74                 } else if (request.isAsyncSupported()) {
 75                     AsyncContext actx = request.startAsync();
 76                     actx.addListener(HawkContext.getContext());
 77                     actx.setTimeout(timeout);
 78                     HawkContext.getContext().addClient(actx);
 79                     HttpServletRequest req = (HttpServletRequest) actx.getRequest();
 80                     HttpServletResponse resp = (HttpServletResponse) actx.getResponse();
 81                     bundle.setRequest(req);
 82                     bundle.setResponse(resp);
 83                     String local = uri;
 84                     HawkContext.getContext().getThreadPool().invoke(new RecursiveAction() {
 85                         /**
 86                          * 
 87                          */
 88                         private static final long serialVersionUID = 1L;
 89 
 90                         @Override
 91                         protected void compute() {
 92                             // TODO Auto-generated method stub
 93                             try {
 94                                 handleService(local, bundle, req, resp);
 95                                 bundle.clear();
 96                             } catch (Exception e) {
 97                                 // TODO Auto-generated catch block
 98                                 e.printStackTrace();
 99                             }
100                         }
101 
102                     });
103                     actx.complete();
104 
105                 } else {
106                     new Exception("Async Not Supported").printStackTrace();
107                     response.sendError(400, "Async is not supported.");
108                 }
109             }
110         } else
111             response.sendError(404, uri);
112 
113     }
114 
115     private void handleService(String requestUri, Bundle bundle, HttpServletRequest request,
116             HttpServletResponse response) throws IOException, ServletException {
117         // TODO Auto-generated method stub
118         requestUri = "work://" + requestUri;
119         String nextUrl = null;
120         URI u = null;
121         try {
122             nextUrl = HawkContext.workStream(requestUri, bundle);
123             if (Objects.isNull(nextUrl))
124                 return;
125             //JSON Literal
126             if (nextUrl.startsWith("{") && nextUrl.endsWith("}") || nextUrl.startsWith("[") && nextUrl.endsWith("]")) {
127                 response.setContentType(Scheme.json.getMime() + ";charset=utf-8");
128                 response.getWriter().append(nextUrl).flush();
129                 return;
130             }
131             u = new URI(nextUrl);
132             //String Literal
133             if(StringX.isEmpty(u.getScheme())) {
134                 response.setContentType(Scheme.text.getMime() + ";charset=utf-8");
135                 response.getWriter().append(nextUrl).flush();
136                 return;
137             }
138         }catch (Exception e) {
139             // TODO Auto-generated catch block
140             e.printStackTrace();
141             throw new ServletException(e);
142         }
143         String path = u.toString().replaceFirst(u.getScheme() + "://", "");
144         switch (Scheme.valueOf(u.getScheme().toLowerCase().trim())) {
145         case xml:
146             response.setContentType(Scheme.xml.getMime() + ";charset=utf-8");
147             Object xml = bundle.getXml();
148             if (Objects.nonNull(xml)) {
149                 OutputStream out = response.getOutputStream();
150                 javax.xml.bind.JAXB.marshal(xml, out);
151                 out.flush();
152             } else if (Objects.nonNull(path))
153                 request.getRequestDispatcher(path).forward(request, response);
154             break;
155         case json:
156             response.setContentType(Scheme.json.getMime() + ";charset=utf-8");
157             Object json = bundle.getJson();
158             if (Objects.nonNull(json)) {
159                 response.getWriter().append(json.toString()).flush();
160             } else if (Objects.nonNull(path))
161                 request.getRequestDispatcher(path).forward(request, response);
162             break;
163         case sse:
164             response.setContentType(Scheme.sse.getMime() + ";charset=utf-8");
165             String text = bundle.getText();
166             if (StringX.isEmpty(text))
167                 text = u.getQuery();
168             if (StringX.nonEmpty(text)) {
169                 response.getWriter().append("data:" + text + "\r\n").flush();
170             }
171             break;
172         case text:
173             response.setContentType(Scheme.text.getMime() + ";charset=utf-8");
174             text = bundle.getText();
175             if (StringX.nonEmpty(text)) {
176                 response.getWriter().append(text).flush();
177             } else if (StringX.nonEmpty(path))
178                 request.getRequestDispatcher(path).forward(request, response);
179             break;
180         case binary:
181             response.setContentType(Scheme.binary.getMime() + ";charset=utf-8");
182             byte[] bytes = bundle.getBytes();
183             if (Objects.nonNull(bytes)) {
184                 OutputStream out = response.getOutputStream();
185                 out.write(bytes);
186                 out.flush();
187             }
188             break;
189         case script:
190             response.setContentType(Scheme.script.getMime() + ";charset=utf-8");
191             text = bundle.getText();
192             if (StringX.nonEmpty(text)) {
193                 response.getWriter().append(text).flush();
194             } else if (StringX.nonEmpty(path))
195                 request.getRequestDispatcher(path).forward(request, response);
196             break;
197         case jsp:
198         case html:
199             response.setContentType(Scheme.html.getMime() + ";charset=utf-8");
200             text = bundle.getText();
201             if (StringX.nonEmpty(text)) {
202                 response.getWriter().append(text).flush();
203             } else if (StringX.nonEmpty(path))
204                 request.getRequestDispatcher(path).forward(request, response);
205             break;
206         case file:
207             request.getRequestDispatcher(path).forward(request, response);
208             break;
209         case http:
210         case https:
211             response.sendRedirect(nextUrl);
212             break;
213         case redirect:
214             response.sendRedirect(path);
215             break;
216         default:
217             break;
218         }
219 
220     }
221 
222     /**
223      * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
224      *      response)
225      */
226     @Override
227     protected void doPost(HttpServletRequest request, HttpServletResponse response)
228             throws ServletException, IOException {
229         // TODO Auto-generated method stub
230         doGet(request, response);
231     }
232 
233     /*
234      * (non-Javadoc)
235      * 
236      * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
237      */
238     @Override
239     public void init(ServletConfig config) throws ServletException {
240         // TODO Auto-generated method stub
241         super.init(config);
242         System.out.println("GlobalController init.");
243     }
244 }

 

posted @ 2017-03-23 13:35  江金汉  阅读(385)  评论(0编辑  收藏  举报