SpringMVC是目前J2EE平臺的主流Web框架,不熟悉的園友能夠看SpringMVC源碼閱讀入門,它交代了SpringMVC的基礎知識和源碼閱讀的技巧html
本文將經過源碼(基於Spring4.3.7)分析,弄清楚Controller是如何匹配咱們傳入的參數,並定義簡單的參數解析器git
demo源碼在這裏,回到DispatcherServlet的doDispatch方法,DispatchServlet分析見SpringMVC源碼閱讀:核心分發器DispatcherServletgithub
doDispatch方法943行獲取了HandlerAdapter,ctrl+h打開類繼承圖,找到RequestMappingHandlerAdapter,RequestMappingHandlerAdapter支持HandlerMethod的方法參數和返回類型,HandlerMethod是3.1版本引入的,爲參數、返回值和註解提供便捷的封裝web
在RequestMappingHandlerAdapter的invokeHandlerMethod方法中,設置ArgumentResolver和ReturnValueHandlerspring
798行和799行給RequestMappingHandlerAdapter定義的ArgumentResolver和ReturnValueHandler賦值,4.2版本之前在createRequstMapping方法,此方法在4.2已被刪除json
827行ServletInvocableHandlerMethod調用invokeAndHandle方法,經過定義的HandlerMethodReturnValueHandler處理返回值,點開invokeAndHandle方法進入ServletInvocableHandlerMethod類瀏覽器
116行處理經過HandlerMethodArgumentResolver來解析參數,132~133行使用註冊過的HandlerMethodReturnValueHandlermvc
afterPropertiesSet方法實現了InitializingBean接口初始化了Handler和Resolver,簡單地說,啓動服務纔會運行afterPropertiesSetapp
517行設置ArgumentResolver,525行設置ReturnValueHandler框架
點看getDefaultArgumentResolvers方法,看看它到底作了什麼
getDefaultArgumentResolvers方法把各類HandlerMethodArgumentResolver放入List並返回
同理,getDefaultReturnValueHandlers方法把各類HandlerMethodReturnValueHandler放入List並返回
如今在回到ServletInvocableHandlerMethod類,咱們發現了returnValueHandlers是HandlerMethodReturnValueHandlerComposite類型的,神祕的HandlerMethodReturnValueHandlerComposite是什麼?
查看類實現圖,咱們發現HandlerMethodReturnValueHandlerComposite繼承HandlerMethodReturnValueHandler
咱們能夠看到,HandlerMethodReturnValueHandlerComposite類裏有HandlerMethodReturnValueHandler類型的list,作過樹結構的園友們應該知道,這裏用到了組合模式,即類包含自身對象組。可是呢,它的類名後面加上了Composite,不能嚴格意義上說是組合模式,能夠說是組合模式的變種,由於HandlerMethodReturnValueHandler是個Interface,因此不是嚴格意義上的「組合模式」
咱們用一樣的思路尋找與HandlerMethodArgumentResolver對應的Composite類,我在ServletInvocableHandlerMethod沒有找到HandlerMethodArgumentResolverComposite,(在4.3版本以前能夠在ServletInvocableHandlerMethod找到),不用擔憂,使用絕招
快捷鍵ctrl+shift+r,用idea強大的全局搜索來找HandlerMethodArgumentResolverComposite的蹤影
這裏注意一下,全局搜索選擇Scope,才能夠在文件全部路徑下搜索(包括Maven源碼包)
最後咱們看到了HandlerMethodArgumentResolverComposite在InvocableHandlerMethod出現,這個類名以爲有些眼熟吧,它是ServletInvocableHandlerMethod的父類
ServletInvocableHandlerMethod調用InvocableHandlerMethod的invokeForRequest方法中使用了HandlerMethodArgumentResolverComposite
打開HandlerMethodArgumentResolverComposite,和HandlerMethodReturnValueHandlerComposite相似,使用組合模式的變種
好了,參數解析基本流程完畢,咱們如今來具體看看支持和參數相映射的註解的參數解析類,對着HandlerMethodArgumentResolver按ctrl+h
能夠看到龐大的類繼承圖,咱們看支持@RequestBody的RequestResponseBodyMethodProcessor類
可能會有園友好奇,爲何我知道RequestResponseBodyMethodProcessor類支持@RequestBody?
一個簡便的方法是直接看類名,開源項目Spring的代碼質量很是高,它們的類名言簡意賅,看類名大概就知道它是作什麼的;類名若是看不出來,就點進去看註釋,註釋很規範、詳細
打開RequestResponseBodyMethodProcessor類
支持帶有@RequestBody的參數,支持帶有@ResponseBody的返回值
寫一個方法進行測試
@RequestMapping(value = "/testRb",produces={"application/json; charset=UTF-8"},method = RequestMethod.POST) @ResponseBody public Employee testRb(@RequestBody Employee e) { return e; }
http://localhost:8080/springmvcdemo/test/testRb,傳入參數爲{"age":1,"id":2},我用的Postman測試請求,直接瀏覽器地址欄輸入,默認Get請求會報錯,不嫌麻煩能夠本身手寫Ajax,參數類型設置成Json測試
header寫成application/json,請求類型寫POST
Body傳入Json格式參數
如今咱們進入resolveArgument方法
127行獲取參數信息,128行調用readWithMessageConverters方法獲取參數值
131行建立WebDataBinder,用於校驗數據格式是否正確
點開128行readWithMessageConverters方法,看看它作什麼
148行獲取請求信息,如頭信息
咱們看到Content-Type正是咱們在Postman中設置的"application/json"
150行獲取參數,調用父類AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法,父類方法用於從請求信息中讀取方法參數值
152行查看參數註解是不是@RequestBody
繼續深刻,進入AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters方法
167行從Headers取得Content-Type
172~175行若是Content-Type爲空,默認給咱們Content-Type設置"application/octet-stream"
185行獲取Http請求方法
191行用消息轉換器讀取請求體
接下來,咱們分析下經常使用的@RequestParam註解是如何處理參數的
用這個測試方法
@RequestMapping("/auth") public String auth(@RequestParam String username, HttpServletRequest req) { req.getSession().setAttribute("loginUser", username); return "redirect:/"; }
找到RequestParamMethodArgumentResolver類,該類的核心方法是resolveName方法
158行獲取請求信息
159行獲取MultipartHttpServletRequest請求信息,用於文件上傳
175行獲取參數值
打斷點我發現,在RequestParamMethodArgumentResolver的父類AbstractNamedValueMethodArgumentResolver中,resolveArgument方法會先執行。後續咱們自定義的參數解析器主要就是重寫resolveArgument方法
97行獲取參數名稱
103行調用resolveName方法獲取參數值,該方法被AbstractNamedValueMethodArgumentResolver子類RequestParamMethodArgumentResolver實現,剛纔咱們已經分析過
我再說下其餘經常使用的HandlerArgumentResolver實現類,就不源碼分析了,有時間我會補充上,園友能夠自行打斷掉調試查看之
1.PathVariableMethodArgumentResolver
支持帶有@PathVariable註解的參數,用來得到請求url中的動態參數
2.MatrixVariableMethodArgumentResolver
支持帶有@MatrixVariable註解的參數,顧名思義,矩陣變量,多個變量可使用「;」分隔
3.RequestParamMethodArgumentResolver
支持帶有@RequestParam註解的參數,也支持MultipartFile類型的參數,本文已分析
4.RequestResponseBodyMethodProcessor
支持帶有@RequestBody、@ResponseBody註解的參數,本文已分析
再看看經常使用的HandlerMethodReturnValueHandler
1.ModelAndViewMethodReturnValueHandler
返回ModelAndView,把view和model信息賦值給ModelAndViewContainer
2.ViewMethodReturnValueHandler
返回View
3.HttpHeadersReturnValueHandler
返回HttpHeaders
4.SteamingResponseBodyReturnValueHandler
返回ResponseEntity<StreamingResponseBody>
@RequestMapping(value = "/testRb",produces={"application/json; charset=UTF-8"},method = RequestMethod.POST) @ResponseBody public Employee testRb(@RequestBody Employee e) { return e; } @RequestMapping(value="/testCustomObj", produces={"application/xml; charset=UTF-8"},method = RequestMethod.GET) @ResponseBody public XmlActionResult<Employee> testCustomObj(@RequestParam(value = "id") int id, @RequestParam(value = "name") String name) { XmlActionResult<Employee> actionResult = new XmlActionResult<Employee>(); Employee e = new Employee(); e.setId(id); e.setName(name); e.setAge(20); e.setDept(new Dept(2,"部門")); actionResult.setCode("200"); actionResult.setMessage("Success with XML"); actionResult.setData(e); return actionResult; } @RequestMapping(value = "/testCustomObjWithRp", produces={"application/json; charset=UTF-8"}) @ResponseBody public Employee testCustomObjWithRp(Employee e) { return e; } @RequestMapping(value = "/testDate", produces={"application/json; charset=UTF-8"}) @ResponseBody public Date testDate(Date date) { return date; }
在Postman中輸入請求http://localhost:8080/springmvcdemo/test/testRb
發出請求,進入了RequestResponseBody的resolveArgument方法,參數咱們能夠看到
源碼分析參照 2.源碼分析
在瀏覽器中輸入http://localhost:8080/springmvcdemo/test/testCustomObjWithRp?id=1&name=s
返回結果以下,返回的是XML格式,(下一部分我再敘述MessageConverter部分的知識,咱們這裏只關注@RequestParam)
輸入請求後,進入了RequestParamMethodArgumentResolver的父類AbstracNamedValueMethodArgumentResolver的reloveArgument方法,因咱們有兩個@RequestParam,會進入reloveArgument兩次
瀏覽器輸入請求http://localhost:8080/springmvcdemo/test/testCustomObjWithRp?id=1&name=s,返回結果以下
無註解咱們怎麼找究竟是哪一個HandlerMethodArgumentResolver實現類在處理呢?只要你認真看了第二部分源碼分析,相信你能夠輕鬆找到
在HandlerMethodArgumentResolverComposite(HandlerMethodArgumentResolver的實現類)第117行getArgumentResolver方法打上斷點,看看廬山真面目
原來,是ServletModelAttributeMethodProcessor爲咱們處理了自定義對象
在瀏覽器輸入請求http://localhost:8080/springmvcdemo/test/testDate?date=2018-01-30
在當前Controller加入InitBinder,使參數規範化傳遞
//自定義屬性編輯器——日期 @InitBinder public void initBinderDate(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false)); }
返回了一個Unix時間戳
在HandlerMethodArgumentResolverComposite的resolveArugument方法打斷點,發現被RequestParamMethodArgumentResovler所解析
在AbstracNamedValueMethodArgumentResolver的reloveArgument方法找到了咱們的參數,方法同測試3.2
自定義參數註解TestObj
@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface TestObj { //參數別名 String value() default ""; }
自定義參數解析器TestObjArgumentResolver實現HandlerMethodArgumentResolver,解決兩個自定義類參數傳參的問題
public class TestObjArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(TestObj.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { TestObj testObj = parameter.getParameterAnnotation(TestObj.class); String alias = getAlias(testObj, parameter); //拿到obj, 先從ModelAndViewContainer中拿,若沒有則new1個參數類型的實例 Object obj = (mavContainer.containsAttribute(alias)) ? mavContainer.getModel().get(alias) : createAttribute(parameter); //得到WebDataBinder,這裏的具體WebDataBinder是ExtendedServletRequestDataBinder WebDataBinder binder = binderFactory.createBinder(webRequest, obj, alias); Object target = binder.getTarget(); if(target != null) { //綁定參數 bindParameters(webRequest, binder, alias); //JSR303 驗證 validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors()) { if (isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } } } return target; } private Object createAttribute(MethodParameter parameter) { return BeanUtils.instantiateClass(parameter.getParameterType()); } //綁定參數 private void bindParameters(NativeWebRequest request, WebDataBinder binder, String alias) { ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class); MockHttpServletRequest newRequest = new MockHttpServletRequest(); Enumeration<String> enu = servletRequest.getParameterNames(); while(enu.hasMoreElements()) { String paramName = enu.nextElement(); if(paramName.startsWith(alias)) { newRequest.setParameter(paramName.substring(alias.length()+1), request.getParameter(paramName)); } } ((ExtendedServletRequestDataBinder)binder).bind(newRequest); } protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) { Annotation[] annotations = parameter.getParameterAnnotations(); for (Annotation annot : annotations) { if (annot.annotationType().getSimpleName().startsWith("Valid")) { Object hints = AnnotationUtils.getValue(annot); binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints}); break; } } } protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) { int i = parameter.getParameterIndex(); Class<?>[] paramTypes = parameter.getMethod().getParameterTypes(); boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1])); return !hasBindingResult; } //生成別名 private String getAlias(TestObj testObj, MethodParameter parameter) { //獲得TestObj的屬性value,也就是對象參數的簡稱 String alias = testObj.value(); if(alias == null || StringUtils.isBlank(alias)) { //若是簡稱爲空,取對象簡稱的首字母小寫開頭 String simpleName = parameter.getParameterType().getSimpleName(); alias = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1); } return alias; } }
dispatcher-servlet.xml加入咱們自定義的參數解析器
<property name="customArgumentResolvers"> <list> <bean class="org.format.demo.custom.TestObjArgumentResolver" /> </list> </property>
測試Controller
@Controller @RequestMapping(value = "/foc") public class TestObjController { @RequestMapping("/test1") @ResponseBody public Map test1(@TestObj Dept dept, @TestObj Employee emp) { Map resultMap = new HashMap(); resultMap.put("Dept",dept); resultMap.put("Emp",emp); return resultMap; } @RequestMapping("/test2") @ResponseBody public Map test2(@TestObj("d") Dept dept, @TestObj("e") Employee emp) { Map resultMap = new HashMap(); resultMap.put("d",dept); resultMap.put("e",emp); return resultMap; } }
瀏覽器輸入http://localhost:8080/springmvcdemo/foc/test1?dept.id=1&dept.name=sss&employee.id=3&employee.name=ddf&employee.age=12
TestObjArgumentResolver中getAlias方法獲取別名
返回結果以下
瀏覽器輸入http://localhost:8080/springmvcdemo/foc/test2?d.id=1&d.name=sss&e.id=3&e.name=ddf&e.age=12
參數別名用咱們自定義的d和e
兩大接口:HandlerMethodArgumentResolver,HandlerMethodReturnValueHandler
ServletInvocableHandlerMethod調用invokeAndHandle方法,使用HandlerMethodReturnValueComposite,使用組合模式,放入HandlerMethodReturnValueHandler的list
同理HandlerMethodArgumentResolverComposite使用組合模式,放入HandlerMethodArgumentResolver的list
在RequestMappingHandlerAdapter中invokeHandlerMethod給ArgumentResolvers和ReturnValueHandlers賦值(4.2之前在createRequstMapping方法,此方法已刪除)
afterPropertiesSet方法注入ArgumentResolvers和ReturnValueHandlers到Spring容器
getDefaultArgumentResolvers設置默認的ArgumentResolvers
getDefaultReturnValueHandlers設置默認的ReturnValueHandlers
RequestResponseBodyMethodProcessor負責解析Controller裏@RequestBody,支持響應類型是@ResponseBody
RequestParamMethodArgumentResolver負責解析Controller裏@RequestParam
無註解狀況若是是簡單對象(如Date,Integer,Doubule等),由RequestParamMethodArgumentResovler處理,複雜對象(如自定義類)由ServletModelAttributeMethodProcessor處理
resolveArgument解析參數類型和值
文章不免有不足之處,歡迎指正