系列文章目錄
前言
spring mvc 它解決了什么問題呢?
1.URL映射
2. 表單參數映射
3. 調用目標Control
4. 數據模型映射
5. 視圖解析
6. 異常處理
上述解決在spring mvc 中都體現在如下組件當中
HandlerMapping: url與控制器的映謝
HandlerAdapter: 控制器執行適配器
ViewResolver:視圖倉庫
view:具體解析視圖
HandlerExceptionResolver:異常捕捕捉器
HandlerInterceptor:攔截器
各組件調用關系流程圖如下:
一、handler
真正處理請求的對象,有4種
1)實現了Controller接口的Bean對象
2)實現了HttpRequestHandler接口的Bean對象
3)@Controller注解的類中添加了@RequestMapping注解的方法(HandlerMethod)
4)HandlerFunction對象
其實handler是一個邏輯概念,并沒有一個統一的接口,上面4中對象都可以成為handler,代碼中是用Object來引用的,后面一篇會具體介紹
二、handlerMapping
有3種
1)BeanNameUrlHandlerMapping:負責Controller接口和HttpRequestHandler接口
2)RequestMappingHandlerMapping:負責@RequestMapping的方法
3)RouterFunctionMapping:負責RouterFunction以及其中的HandlerFunction
1.handlerMapping的重要結構
1)存儲請求和handler的映射關系
例如RequestMappingHandlerMapping存儲了2個map
a. mappingLookup:key:RequestMappingInfo value:HandlerMethod
RequestMappingInfo 是對@RequestMapping注解中元數據的封裝
HandlerMethod 是對method和目標對象的封裝,是處理器真正執行的地方
b. urlLookup:key:url value:List
url和RequestMappingInfo是多對多的關系,比如@RequestMapping 注解可以配置多個URL路徑來映射到同一個控制器方法
@Controller
public class MyController {@RequestMapping(value = {"/path1", "/path2", "/path3"})public String handleMultiplePaths() {return "viewName";}
}
一個URL可以通過請求方式映射多個控制器方法
@Controller
public class MyController {@RequestMapping(value = "/path",method = "GET")public String getResource() {return "viewName";}@RequestMapping(value = "/path",method = "POST")public String postResource() {return "viewName";}
}
至于具體怎么通過這2個map找到對應的處理器,下一篇再講
2)存儲攔截器
在處理請求時,與handler一起組成執行鏈executionChain
2.如何獲取handlerMapping
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws
Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;
三、handlerAdapter
不同種類的Handler處理請求的方法不一樣,那么需要一個能將他們統一調用的東西,那就是適配器
針對不同的Handler,會有不同的適配器:
HttpRequestHandlerAdapter:對應實現了HttpRequestHandler的處理器
SimpleControllerHandlerAdapter:對應實現了Controller接口的處理器
RequestMappingHandlerAdapter:對應@RequestMapping注解方法的處理器
HandlerFunctionAdapter:對應實現了 HandlerFunction接口的處理器
尋找適配器的邏輯如下,也是策略模式
rotected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { for (HandlerAdapter adapter : this.handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } } 。。。}
四、HandlerMethodArgumentResolver
SpringMVC要去解析方法參數,那么該如何解析呢,是通過HandlerMethodArgumentResolver來進行的,比如對于@RequestMapping方法
RequestParamMethodArgumentResolver:負責處理@RequestParam
RequestHeaderMethodArgumentResolver:負責處理@RequestHeader
SessionAttributeMethodArgumentResolver:負責處理@SessionAttribute
RequestAttributeMethodArgumentResolver:負責處理@RequestAttribute
RequestResponseBodyMethodProcessor:負責處理@RequestBody
而在判斷某個參數該由哪個HandlerMethodArgumentResolver處理時,也是用的策略模式:
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) { if (resolver.supportsParameter(parameter)) { result = resolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; }
五、HandlerMethodReturnValueHandler
方法返回值如何解析呢,比如返回一個String,加了@ResponseBody注解直接將這個String返回給瀏覽器,沒有加就根據這個String找到對應的頁面,把頁面返回給瀏覽器。這些處理是通過HandlerMethodReturnValueHandler來進行的,比如下面的實現:
1)RequestResponseBodyMethodProcessor:處理加了@ResponseBody注解的情況
2)ViewNameMethodReturnValueHandler:處理沒有加@ResponseBody注解并且返回值類型為String的情況
3)ModelMethodProcessor:處理返回值是Model類型的情況
如何判斷使用哪個來解析呢,還是策略模式:
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {boolean isAsyncValue = isAsyncReturnValue(value, returnType);for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {continue;}if (handler.supportsReturnType(returnType)) {return handler;}}return null;}
六、HttpMessageConverter
RequestResponseBodyMethodProcessor,因為它會處理加了@ResponseBody注解的情況,也是目前我們用得最多的情況。如果@ResponseBody注解的方法返回的不是String,而是Map、User對象呢,該怎么解析呢?
SpringMVC會利用HttpMessageConverter來處理,默認情況下有4種:
1)ByteArrayHttpMessageConverter:處理返回值為字節數組的情況,把字節數組返回給瀏覽器
2)StringHttpMessageConverter:處理返回值為字符串的情況,把字符串按指定的編碼序列號后返回給瀏覽器
3)SourceHttpMessageConverter:處理返回值為XML對象的情況,比如把DOMSource對象返回給瀏覽器
4)AllEncompassingFormHttpMessageConverter:處理返回值為MultiValueMap對象的情況
不過以上四個Converter是不能處理Map對象或User對象的,所以如果返回的是Map或User對象,那
么得單獨配置一個Converter,比如MappingJackson2HttpMessageConverter,這個Converter比
較強大,能把String、Map、User對象等等都能轉化成JSON格式。
public class AppConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { MappingJackson2HttpMessageConverter messageConverter = new
MappingJackson2HttpMessageConverter();messageConverter.setDefaultCharset(StandardCharsets.UTF_8); converters.add(messageConverter); } }
另外,如果使用StringHttpMessageConverter,字符集默認為ISO-8859-1,所以默認情況下返
回中文會亂碼,需要通過配置解決
ublic class AppConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { 7StringHttpMessageConverter messageConverter = new
StringHttpMessageConverter();messageConverter.setDefaultCharset(StandardCharsets.UTF_8); converters.add(messageConverter); } }