自己實現spring核心功能 三

前言

 前兩篇已經基本實現了spring的核心功能,下面講到的參數綁定是屬於springMvc的範疇了。本篇主要將請求到servlet後怎麼去做映射和處理。首先來看一看dispatherServlet的基本流程,這我在以前的博客裏面也講過,傳送門

這裏先給個我們的簡易處理流程

 

準備工作

 爲了能將請求傳遞,我們需要寫一個控制器類來接收請求,寫兩個接口來處理請求

HomeController類
 1 @JCController
 2 @JCRequestMapping("/home")
 3 public class HomeController {
 4     @JCAutoWrited
 5     private IHomeService homeService;
 6 
 7     @JCRequestMapping("/sayHi")
 8     public String sayHi() {
 9         return homeService.sayHi();
10     }
11 
12     @JCRequestMapping("/getName")
13     public String getName(Integer id,String no) {
14         return homeService.getName(id,no);
15     }
16     @JCRequestMapping("/getRequestBody")
17     public String getRequestBody(Integer id, String no, GetUserInfo userInfo) {
18         return homeService.getRequestBody(id,no,userInfo);
19     }
20 }
View Code
HomeService類
 1 @JCService
 2 public class HomeService  implements IHomeService{
 3 
 4     @JCAutoWrited
 5      StudentService studentService;
 6     @Override
 7     public String sayHi() {
 8       return   studentService.sayHi();
 9     }
10 
11     @Override
12     public String getName(Integer id,String no) {
13         return "SB0000"+id;
14     }
15 
16     @Override
17     public String getRequestBody(Integer id, String no, GetUserInfo userInfo) {
18         return "userName="+userInfo.getName()+" no="+no;
19     }
20 }
View Code
StudentService類
1 @JCService
2 public class StudentService  implements IStudentService{
3     @Override
4     public String sayHi(){
5         return "Hello world!";
6     }
7 }
View Code

 

無參請求過程

根據上面的圖,我們在瀏覽器發起請求localhost:8080/home/sayHi,請求會到達JCDispatherServlet類,由於我們是GET請求

 @Override
 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     doPost(req, resp);
 }
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
doDispatcherServlet(req, resp);
} catch (Exception e) {
e.printStackTrace();
}

}

會走到doDispatcherServlet方法裏面處理請求

 1     void doDispatcherServlet(HttpServletRequest req, HttpServletResponse resp) throws Exception {
 2         String url = req.getRequestURI();
 3         url = url.replace(req.getContextPath(), "").replaceAll("/+", "/");
 4         if (!urlMapping.containsKey(url)) {
 5             resp.getWriter().write("404! url is not found!");`
 6             return;
 7         }
 8 
 9         Method method = urlMapping.get(url);
10         String className = 
11         method.getDeclaringClass().getSimpleName();
12         className = firstLowerCase(className);
13         if (!ioc.containsKey(className)) {
14             resp.getWriter().write("500! claas not defind !");
15             return;
16         }
17         Object[] args=null ;
18 
19         //調用目標方法
20         Object res = method.invoke(ioc.get(className), args);
21 
22         resp.setContentType("text/html;charset=utf-8");
23         resp.getWriter().write(res.toString());
24     }

第九行代碼會以url爲key從HashMap取出數據,返回Method對象,它對應到我們在HomeController中定義的方法public String sayHi() 。

public Object invoke(Object obj, Object... args) 

通過反射調用方法,需要2個參數,第一個是方法所在類的對象,一個是方法所需要的參數。

下面的代碼就是在ioc容器中取HomeController類對象,如果沒有,就拋500錯誤。

Method method = urlMapping.get(url);
String className = method.getDeclaringClass().getSimpleName();
className = firstLowerCase(className);
if (!ioc.containsKey(className)) {
resp.getWriter().write("500! claas not defind !");
return;
}

//調用目標方法
Object res = method.invoke(ioc.get(className), null);
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().write(res.toString());

 

可以看到,無參請求args傳過去null,然後把調用結果返回,瀏覽器打印結果,證明無參可以使用了。

接下來就需要接收參數的傳遞了。

參數傳遞

 我們常用參數傳遞大致分爲三種,GET傳參,POST方式form傳參,POST方式json傳參。

 

  前兩種傳參都可以通過HttpServletRequest的getParameterMap()獲取到,它返回的是Map<String, String[]>結構,我們需要遍歷map,拿到裏面的key和值。

 @JCRequestMapping("/getName")
  public String getName(Integer id,String no) {
        return homeService.getName(id,no);
    }

我們在瀏覽器請求輸入http://localhost:8080/home/getName?id=11&no=lisi

傳過來的是這樣一個數據,裏面有字段名稱,有字段的value

 而json格式的參數,需要從輸入流裏面獲取 req.getInputStream() 

 知道了傳進來的字段名稱後,現在有2個問題,一個是方法參數的類型,一個是方法參數的順序,這就涉及到了參數的綁定

 

參數綁定

 什麼叫參數綁定,舉個例子

從這個方法的聲明,可以看到,第一個參數要求是名稱爲id且類型爲Integer,第二個參數要求名稱爲no且類型爲String。

我們需要把request傳進來的參數列表,按照方法的要求,一個一個傳進去,不能少也不能類型錯亂。

要完成這個要求,我們首先需要獲得方法的形參列表,其次要把參數按順序,按類型給組裝好。

1.獲取形參列表

2.按要求組裝好參數

 

獲取形參列表

從這裏可以看到 獲取到的參數個數是正常的,類型也沒有問題,但字段名稱顯然是錯誤的,咱們正確的字段名稱應該是id、no

通過網上可以查到,這個需要達到3個要求才能正常使用。

1.jdk1.8 

2.在idea設置參數 -parameters

3.Build->RebuildProject

 設置方法博客地址

 還有個前提,每次JCDispatherServlet代碼變更,需要重新編譯項目Build->RebuildProject 生成最新的代碼

 重新編譯後,調試結果如下

到此獲取形參的工作已經做好了,只需要循環parameters數組就好了。

類型轉換

不過還有個比較棘手的問題

我們發現,從request獲取到的實參都是String數組類型,需要根據形參轉成指定類型,而且只能通過反射轉換。

 所以一番折騰後,把入參轉換成指定類型的實參的代碼如下

 1   Object getInstanceField(Parameter parameter, String value) {
 2         if (parameter.getName().isEmpty()) {
 3             return null;
 4         }
 5         Class typeClass = parameter.getType();
 6         Constructor con = null;
 7         try {
 8             con = typeClass.getConstructor(value.getClass());
 9             return con.newInstance(value);
10         } catch (InvocationTargetException e) {
11             e.printStackTrace();
12         } catch (IllegalAccessException e) {
13             e.printStackTrace();
14         } catch (InstantiationException e) {
15             e.printStackTrace();
16         } catch (NoSuchMethodException e) {
17             e.printStackTrace();
18         }
19         return null;
20     }
21 
22     Object[] doPostParam(HttpServletRequest req, Method method) {
23         Parameter[] parameters = method.getParameters();
24         Object[] requestParam = new Object[parameters.length];
25         int i = 0;
26         for (Parameter p : parameters) {
27             requestParam[i] = null;
28             if (!p.getName().isEmpty()) {
29                 requestParam[i] = getInstanceField(p, req.getParameter(p.getName()));
30             }
31             i++;
32         }
33         return requestParam;
34     }
35 
36     Object[] doJsonParam(String json, Method method) {
37         if (null == json || json.isEmpty()) {
38             return null;
39         }
40         Parameter[] parameters = method.getParameters();
41         Object[] requestParam = new Object[parameters.length];
42         JSONObject jsonObject = JSONObject.parseObject(json);
43         int i = 0;
44         for (Parameter p : parameters) {
45             Object val = jsonObject.getObject(p.getName(), p.getType());
46             requestParam[i] = val;
47             i++;
48         }
49         return requestParam;
50     }
51 
52     Object[] doGetParam(Map<String, String[]> map, Method method) {
53         if (null == map || map.size() == 0) {
54             return null;
55         }
56         Parameter[] parameters = method.getParameters();
57         int i = 0;
58         Object[] requestParam = new Object[parameters.length];
59         for (Parameter p : parameters) {
60             requestParam[i] = null;
61             if (map.containsKey(p.getName())) {
62                 String[] values = map.get(p.getName());
63                 requestParam[i] = getInstanceField(p, values[0]);
64             }
65             i++;
66         }
67         return requestParam;
68     }
View Code

 

返回Object[],裏面的類型和順序需要保證準確 

瀏覽器調用結果Get請求

 

也可以用PostMan 發起Post請求

 

 這兩種都每辦法傳對象,而我們開發者需要傳遞對象。所以,再加個接口,測試包含對象時的混合綁定

 

對象類型參數綁定

 

   @JCRequestMapping("/getRequestBody")
    public String getRequestBody(Integer id, String no, GetUserInfo userInfo) {
        return homeService.getRequestBody(id,no,userInfo);
    }

 
doDispatcherServlet() 處理請求需要根據請求方式做不同處理,改造後如下
 1       Object[] args;
 2         if ("GET".equalsIgnoreCase(req.getMethod())) {
 3             args = doGetParam(req.getParameterMap(), method);
 4         } else if ("POST".equalsIgnoreCase(req.getMethod()) && req.getContentType().contains("json")) {
 5             String str = getJson(req);
 6             args = doJsonParam(str, method);
 7         } else {
 8             args = doPostParam(req, method);
 9         }
10         //調用目標方法
11         Object res = method.invoke(ioc.get(className), args);

傳對象只能通過json方式傳進來,所以我們postMan請求json格式數據

處理json請求

請求地址 請求方式 請求參數
http://localhost:8080/home/getRequestBody application/json {"id":11,"no":"SB00011","userInfo":{"name":"小提莫","age":20}}

 

 

 

json請求核心代碼就是使用fastjson根據字段名取值

 1     Object[] doJsonParam(String json, Method method) {
 2         if (null == json || json.isEmpty()) {
 3             return null;
 4         }
 5         Parameter[] parameters = method.getParameters();
 6         Object[] requestParam = new Object[parameters.length];
 7         JSONObject jsonObject = JSONObject.parseObject(json);
 8         int i = 0;
 9         for (Parameter p : parameters) {
10             Object val = jsonObject.getObject(p.getName(), p.getType());
11             requestParam[i] = val;
12             i++;
13         }
14         return requestParam;
15     }

 返回結果:

 結束

到這裏,servlet處理請求,並響應已經得到驗證,能夠正常的對外提供服務。一個微型的springMvc框架已經完成了。

完整代碼

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