自己實現spring核心功能 三
- 2019 年 10 月 3 日
- 筆記
前言
前兩篇已經基本實現了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框架已經完成了。