寫一個自己的springMVC?

今天我們來實現一個簡單的springMVC框架,可以理解爲 springMVC1.0這個版本,只是功能比較簡單而已;

廢話不多說,進入正題;先看看springMVC的簡單流程;


我們請求的地址會被dispatchservlet這個springMVC的核心攔截器攔截,然後dispatchservlet會找到我們想要的那個controller裏的那個方法,並調用。但是dispatchServlet不是人,它沒那麼智能,看到url就知道是誰了,但是我們可以讓它變得智能起來,我們可以利用handlerMapping來告訴

dispatchServlet,它應該調用哪個方法;

爲了讓這個框架不那麼笨,我借用了spring的IOC 思想, 實現一個容器來管理我的bean; 這個框架和你印象中使用springmvc 很相似,你應該能回想起

springMVC的零零點點,然後看看這個簡單的框架是如何實現這些功能的;

首先看下項目工程:


首先,我們先看maven 依賴,沒錯,自己實現的框架當然不用spring的jar包了;

併爲了方便大家理解,我的取名和spring原來的風格多少有些類似;

首先看到annotation包,@Autowired、@Controller、@RequestMapping、@service這些註解大家應該再熟悉不過了吧!

然後是 servlet包,模仿的springMVC的核心攔截器 dispatchServlet;

demo包的 controller service  不必解釋啦~


這裏我們模仿springMVC的調用規則,MyDispatcherServlet負責處理框架的邏輯,調用Controller,Controller調用service;

先看看自定義註解是如何定義的,這裏挑選了幾個代表;





開始編寫核心攔截器 dispatchServilet


我們第一步模仿spring 的思想,先找到我們要掃描哪些類,下面是 spring的做法,



這是我的做法:


爲什麼我通過  String scanPackage = config.getInitParameter("scanPackage"); 就能找到xml中的配置呢?請參考這裏 的初始化細節;

servlet 對象在初始化的時候,容器會給它提供一個 ServletConfig 對象  去讀取 web.xml中的配置;


我們得到要掃描的路徑後,可以就需要實現spring的IOC了;

我們爲了得到所有bean;在拿到項目的包路徑後,可以轉換爲文件路徑,然後再從文件路徑中得到所有的類名;

得到類名後,就可以通過反射進行實例化了,然後將這些需要管理的東西放到一個容器中管理,要用的時候從容器裏拿就可以了;

我這裏使用的容器是 Hashmap<String, Object>    類的簡稱(SimpleName)爲key ,類的實例對象爲value。


得到了所有的類名後,開始實例化的工作

[java] view plain copy
  1. private void instance(){  
  2.         //利用反射機制將掃描到的類名全部實例化  
  3.         if(classNames.size() == 0){ return; }  
  4.         try{  
  5.             for (String className : classNames) {  
  6.                   
  7.                     Class<?> clazz = Class.forName(className);  
  8.                     //沒有@Controller、@Service註解標識的類不需要實例化  
  9.                     if(clazz.isAnnotationPresent(LANController.class)){  
  10.                         //getSimpleName() 除去包名,獲取類名的簡稱  例如: MyAction  
  11.                         String beanName = lowerFirstChar(clazz.getSimpleName());  
  12.                         instanceMapping.put(beanName, clazz.newInstance());  
  13.                     }else if(clazz.isAnnotationPresent(LANService.class)){  
  14.                         LANService service = clazz.getAnnotation(LANService.class);  
  15.                         String beanName = service.value();  
  16.                         if(!"".equals(beanName.trim())){  
  17.                             //beanName 這裏就是aa  
  18.                             instanceMapping.put(beanName, clazz.newInstance());  
  19.                             continue;  
  20.                         }  
  21.                         //如果自己沒有起名字,後面會通過接口自動注入  
  22.                         Class<?> [] interfaces = clazz.getInterfaces();  
  23.                         for (Class<?> i : interfaces) {  
  24.                             instanceMapping.put(i.getName(), clazz.newInstance());  
  25.                         }  
  26.                     }else{  
  27.                         continue;  
  28.                     }  
  29.             }  
  30.         }catch(Exception e){  
  31.             e.getStackTrace();  
  32.         }  
  33.     }  

實例化以後,就要準備注入了;

[java] view plain copy
  1. private void autowired(){  
  2.         if(instanceMapping.isEmpty()){ return; }  
  3.         for (Entry<String, Object> entry : instanceMapping.entrySet()) {  
  4.             //getDeclaredFields()獲取自己聲明的所有字段  
  5.             Field [] fields = entry.getValue().getClass().getDeclaredFields();  
  6.             for (Field field : fields) {  
  7.                 if(!field.isAnnotationPresent(LANAutowired.class)){ continue; }  
  8.                 LANAutowired autowired = field.getAnnotation(LANAutowired.class);  
  9.                 //如果是私有屬性,設置可以訪問的權限  
  10.                 field.setAccessible(true);  
  11.                 //自己取的名字   獲取註解的值  
  12.                 String beanName = autowired.value().trim();  
  13.                 System.out.println("beanName=="+beanName);  
  14.                 //如果沒有自己取名字  
  15.                 if("".equals(beanName)){  
  16.                     //getType()獲取該字段聲明時的     類型對象   根據類型注入  
  17.                     beanName = field.getType().getName();  
  18.                 }  
  19.                 try {  
  20.                     System.out.println("field.getName()***"+field.getName());  
  21.                      // 注入接口的實現類,    
  22.                      System.out.println("entry.getValue()======"+entry.getValue());  
  23.                      System.out.println("instanceMapping.get(beanName)---------"+instanceMapping.get(beanName));  
  24.                      //將Action 這個 類的 IModifyService 字段設置成爲   aa 代表的實現類  ModifyServiceImpl  
  25.                     field.set(entry.getValue(),instanceMapping.get(beanName));  
  26.                 } catch (Exception e) {  
  27.                     e.printStackTrace();  
  28.                     continue;  
  29.                 }  
  30.             }  
  31.         }  
  32.     }  


[java] view plain copy
  1. field.set(entry.getValue(),instanceMapping.get(beanName));  
這一行代碼是關鍵,這就是爲什麼我們在注入接口,就能找到實現類的根本所在。

這裏的field,就是我們注入的那個接口, entry.getValue() 得到的是接口所在的類,instanceMapping.get(beanName)是這個接口對應的

那個實現類,   意思就是:在 運行階段, 將 controller中 的某個service接口字段 替換成 這個service的實現類;

這樣我們在編寫代碼的時候是用使用接口調用方法,但實際運行時,就是它的實現類在調用這個方法了;

不得不感嘆,反射的強大。


完成注入後,開始處理handlermapping上的value與之對應的method 的映射關係了

[java] view plain copy
  1. public void handlerMapping(){  
  2.         if(instanceMapping.isEmpty()){ return; }  
  3.         for (Entry<String, Object> entry : instanceMapping.entrySet()) {  
  4.             Class<?> clazz = entry.getValue().getClass();  
  5.             //RequestMapping只在 Controller中  
  6.             if(!clazz.isAnnotationPresent(LANController.class)){ continue; }  
  7.             String url = "";  
  8.             if(clazz.isAnnotationPresent(LANRequestMapping.class)){  
  9.                 LANRequestMapping requstMapping = clazz.getAnnotation(LANRequestMapping.class);  
  10.                 //得到RequstMapping的value(/web)準備與 方法上的 RequstMapping(search/add/remove)進行拼接;  
  11.                 url = requstMapping.value();//(/web)  
  12.             }  
  13.             Method [] methods = clazz.getMethods();  
  14.             for (Method method : methods) {  
  15.                 if(!method.isAnnotationPresent(LANRequestMapping.class)){ continue; }  
  16.                 LANRequestMapping requstMapping = method.getAnnotation(LANRequestMapping.class);  
  17.                 String regex =  url + requstMapping.value();  
  18.              // regex = /web/add.json  
  19.                 regex = regex.replaceAll("/+""/").replaceAll("\\*"".*");  
  20.                 System.out.println("regex: "+regex);  
  21.                 Map<String,Integer> pm = new HashMap<String,Integer>();  
  22.                 //因爲每個參數可能有多個註解,所以會是個二維數組  
  23.                 Annotation [] [] pa = method.getParameterAnnotations();  
  24.                 for(int i = 0; i < pa.length; i ++){  
  25.                     for (Annotation a : pa[i]){  
  26.                         if(a instanceof LANRequestParam){  
  27.                             String paramName = ((LANRequestParam) a).value();  
  28.                             if(!"".equals(paramName.trim())){  
  29.                       //方法參數的名字(值)  name/addr,下標  
  30.                                 pm.put(paramName, i);  
  31.                             }  
  32.                         }  
  33.                     }  
  34.                 }  
  35.                 //提取Request和Response的索引  
  36.                 Class<?> [] paramsTypes = method.getParameterTypes();  
  37.                 for(int i = 0 ; i < paramsTypes.length; i ++){  
  38.                     Class<?> type = paramsTypes[i];  
  39.                     if(type == HttpServletRequest.class ||  type == HttpServletResponse.class){  
  40.                           
  41.                         pm.put(type.getName(), i);  
  42.                     }  
  43.                 }  
  44.                 handlerMapping.add(new Handler(Pattern.compile(regex),entry.getValue(),method, pm));  
  45.             }  
  46.         }  
  47.     }  

完成所有的初始化工作後,當然就等着用戶發過來的請求咯。

那自然是調用servlet的 doPost 和 doGet方法了,

爲了簡單點,我在doGet中調用doPost

[java] view plain copy
  1. @Override  
  2.     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {  
  3.         this.doPost(req, resp);  
  4.     }  
  5.   
  6.     @Override  
  7.     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {  
  8.         //System.out.println(req.getRequestURI());  
  9.         try{  
  10.             //從上面已經初始化的信息中匹配  
  11.             //拿着用戶請求url去找到其對應的Method  
  12.             boolean isMatcher = pattern(req,resp);  
  13.         if(!isMatcher){  
  14.             resp.getWriter().write("對不起,你遇到了  404 Not Found");  
  15.         }  
  16.         }catch(Exception e){  
  17.             resp.getWriter().write("500 Exception,Details:\r\n" +   
  18.             e.getMessage() + "\r\n" +  
  19.             Arrays.toString(e.getStackTrace()).replaceAll("\\[\\]""")  
  20.             .replaceAll(",\\s""\r\n"));  
  21.         }  
  22.     }  
如果如果沒有匹配成功就返回404,說明用戶的路徑輸錯了,

發送異常就報500;

如果匹配成功怎麼辦? 當然是調用controller裏的方法咯;

怎麼調用?還是通過反射~通過方法的反射~

[java] view plain copy
  1. public boolean pattern(HttpServletRequest req, HttpServletResponse resp) throws Exception{  
  2.         if(handlerMapping.isEmpty()){  return false; }  
  3.         //獲取請求的url  
  4.         String url = req.getRequestURI();  
  5.         //獲取容器路徑  
  6.         String contextPath = req.getContextPath();  
  7.         url = url.replace(contextPath, "").replaceAll("/+""/");     
  8.         for (Handler handler : handlerMapping) {  
  9.             try{  
  10.                 Matcher matcher = handler.pattern.matcher(url);  
  11.                 //如果沒匹配上就跳出  
  12.                 if(!matcher.matches()){ continue ;}  
  13.                 //按照聲明順序返回  Class 對象的數組,這些對象描述了此 Method 對象所表示的方法的形參類型。  
  14.                 Class<?> [] paramTypes = handler.method.getParameterTypes();  
  15.                 //裏面存放 反射是需要的具體參數  
  16.                 Object [] paramValues = new Object[paramTypes.length];  
  17.                 //獲取前端請求參數和請求參數值的映射關係  
  18.                 Map<String,String[]> params = req.getParameterMap();  
  19.                 for (Entry<String, String []> param : params.entrySet()) {  
  20.                     String value = Arrays.toString(param.getValue()).replaceAll("\\]|\\[""").replaceAll(",\\s"",");  
  21.                     if(!handler.paramMapping.containsKey(param.getKey())){ continue;}  
  22.                     //如果匹配,則獲取該參數在方法中的下標;  
  23.                     int index = handler.paramMapping.get(param.getKey());  
  24.                     //涉及到類型轉換  
  25.                     paramValues[index] = castStringValue(value,paramTypes[index]);  
  26.                 }  
  27.                 //  
  28.                 int reqIndex = handler.paramMapping.get(HttpServletRequest.class.getName());  
  29.                 paramValues[reqIndex] = req;  
  30.                 int respIndex = handler.paramMapping.get(HttpServletResponse.class.getName());  
  31.                 paramValues[respIndex] = resp;  
  32.                 //方法的反射   需要對象.方法  
  33.                 handler.method.invoke(handler.controller, paramValues);  
  34.                 return true;  
  35.             }catch(Exception e){  
  36.                 throw e;  
  37.             }  
  38.         }  
  39.         return false;  
  40.     }  


我們來看看controller的代碼,準備測試:

[java] view plain copy
  1. public class MyController {  
  2.       
  3.     public  MyController() {  
  4.         System.out.println("初始化了"+this);  
  5.     }  
  6.       
  7.     @LANAutowired   
  8.     private searchService searchService;  
  9.       
  10.     @LANAutowired("aa")   
  11.     private modifyService modifyService;  
  12.       
  13.     @LANRequestMapping("/search/*.json")  
  14.     public void search(HttpServletRequest request,HttpServletResponse response,  
  15.             @LANRequestParam("name") String name){  
  16.         String result = searchService.search(name);  
  17.         System.out.println("查詢成功");  
  18.         out(response,result);  
  19.     }  
  20.       
  21.       
  22.     @LANRequestMapping("/add.json")  
  23.     public void add(HttpServletRequest request,HttpServletResponse response,  
  24.             @LANRequestParam("name") String name,  
  25.             @LANRequestParam("addr") String addr){  
  26.         System.out.println("添加成功");  
  27.         String result = modifyService.add(name,addr);  
  28.         out(response,result);  
  29.     }  
  30.       
  31.       
  32.     @LANRequestMapping("/delete.json")  
  33.     public void remove(HttpServletRequest request,HttpServletResponse response,  
  34.             @LANRequestParam("id") Integer id){  
  35.         System.out.println("刪除成功");  
  36.         String result = modifyService.remove(id);  
  37.         out(response,result);  
  38.     }  
是不是和你剛學springMVC時的一致?

趕緊啓動項目在地址欄輸入:http://localhost:8080/LanSpringMVC/web/add.json?name=witt&addr=shenzhen

出現:

[java] view plain copy
  1. add name = witt,addr=shenzhen  
說明大工告成~

想要源碼的小夥伴點這裏:源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章