原理流程圖:https://www.processon.com/view/link/63f1d5cc2f69f86c1f96ee9c
SpringMVC的作用毋庸置疑,雖然我們現在都是用SpringBoot,但是SpringBoot中仍然是在使用SpringMVC來處理請求。
我們在使用SpringMVC時,傳統的方式是通過定義web.xml,比如:
<web-app> <servlet> <servlet-name>app</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>app</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping> </web-app>
我們只要定義這樣的一個web.xml,然後啓動Tomcat,那麼我們就能正常使用SpringMVC了。
SpringMVC中,最爲核心的就是DispatcherServlet,在啓動Tomcat的過程中:
- Tomcat會先創建DispatcherServlet對象
- 然後調用DispatcherServlet對象的init()
而在init()方法中,會創建一個Spring容器,並且添加一個ContextRefreshListener監聽器,該監聽器會監聽ContextRefreshedEvent事件(Spring容器啓動完成後就會發布這個事件),也就是說Spring容器啓動完成後,就會執行ContextRefreshListener中的onApplicationEvent()方法,從而最終會執行DispatcherServlet中的initStrategies(),這個方法中會初始化更多內容:
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
其中最爲核心的就是HandlerMapping和HandlerAdapter。
什麼是Handler?
Handler表示請求處理器,在SpringMVC中有四種Handler:
- 實現了Controller接口的Bean對象
- 實現了HttpRequestHandler接口的Bean對象
- 添加了@RequestMapping註解的方法
- 一個HandlerFunction對象
比如實現了Controller接口的Bean對象:
@Component("/test") public class ZhouyuBeanNameController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("zhouyu"); return new ModelAndView(); } }
實現了HttpRequestHandler接口的Bean對象:
@Component("/test") public class ZhouyuBeanNameController implements HttpRequestHandler { @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("zhouyu"); } }
添加了@RequestMapping註解的方法:
@RequestMapping @Component public class ZhouyuController { @Autowired private ZhouyuService zhouyuService; @RequestMapping(method = RequestMethod.GET, path = "/test") @ResponseBody public String test(String username) { return "zhouyu"; } }
一個HandlerFunction對象(以下代碼中有兩個):
@ComponentScan("com.zhouyu") @Configuration public class AppConfig { @Bean public RouterFunction<ServerResponse> person() { return route() .GET("/app/person", request -> ServerResponse.status(HttpStatus.OK).body("Hello GET")) .POST("/app/person", request -> ServerResponse.status(HttpStatus.OK).body("Hello POST")) .build(); } }
什麼是HandlerMapping?
HandlerMapping負責去尋找Handler,並且保存路徑和Handler之間的映射關係。
因爲有不同類型的Handler,所以在SpringMVC中會由不同的HandlerMapping來負責尋找Handler,比如:
- BeanNameUrlHandlerMapping:負責Controller接口和HttpRequestHandler接口
- RequestMappingHandlerMapping:負責@RequestMapping的方法
- RouterFunctionMapping:負責RouterFunction以及其中的HandlerFunction
BeanNameUrlHandlerMapping的尋找流程:
- 找出Spring容器中所有的beanName
- 判斷beanName是不是以“/”開頭
- 如果是,則把它當作一個Handler,並把beanName作爲key,bean對象作爲value存入handlerMap中
- handlerMap就是一個Map
RequestMappingHandlerMapping的尋找流程:
- 找出Spring容器中所有beanType
- 判斷beanType是不是有@Controller註解,或者是不是有@RequestMapping註解
- 判斷成功則繼續找beanType中加了@RequestMapping的Method
- 並解析@RequestMapping中的內容,比如method、path,封裝爲一個RequestMappingInfo對象
- 最後把RequestMappingInfo對象做爲key,Method對象封裝爲HandlerMethod對象後作爲value,存入registry中
- registry就是一個Map
RouterFunctionMapping的尋找流程會有些區別,但是大體是差不多的,相當於是一個path對應一個HandlerFunction。
各個HandlerMapping除開負責尋找Handler並記錄映射關係之外,自然還需要根據請求路徑找到對應的Handler,在源碼中這三個HandlerMapping有一個共同的父類AbstractHandlerMapping
AbstractHandlerMapping實現了HandlerMapping接口,並實現了getHandler(HttpServletRequest request)方法。
AbstractHandlerMapping會負責調用子類的getHandlerInternal(HttpServletRequest request)方法從而找到請求對應的Handler,然後AbstractHandlerMapping負責將Handler和應用中所配置的HandlerInterceptor整合成爲一個HandlerExecutionChain對象。
所以尋找Handler的源碼實現在各個HandlerMapping子類中的getHandlerInternal()中,根據請求路徑找到Handler的過程並不複雜,因爲路徑和Handler的映射關係已經存在Map中了。
比較困難的點在於,當DispatcherServlet接收到一個請求時,該利用哪個HandlerMapping來尋找Handler呢?看源碼:
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; }
很簡單,就是遍歷,找到就返回,默認順序爲:
所以BeanNameUrlHandlerMapping的優先級最高,比如:
@Component("/test") public class ZhouyuBeanNameController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("Hello zhouyu"); return new ModelAndView(); } }
@RequestMapping(method = RequestMethod.GET, path = "/test") @ResponseBody public String test(String username) { return "Hi zhouyu"; }
請求路徑都是/test,但是最終是Controller接口的會生效。
什麼是HandlerAdapter?
找到了Handler之後,接下來就該去執行了,比如執行下面這個test()
@RequestMapping(method = RequestMethod.GET, path = "/test") @ResponseBody public String test(String username) { return "zhouyu"; }
但是由於有不同種類的Handler,所以執行方式是不一樣的,再來總結一下Handler的類型:
- 實現了Controller接口的Bean對象,執行的是Bean對象中的handleRequest()
- 實現了HttpRequestHandler接口的Bean對象,執行的是Bean對象中的handleRequest()
- 添加了@RequestMapping註解的方法,具體爲一個HandlerMethod,執行的就是當前加了註解的方法
- 一個HandlerFunction對象,執行的是HandlerFunction對象中的handle()
所以,按邏輯來說,找到Handler之後,我們得判斷它的類型,比如代碼可能是這樣的:
Object handler = mappedHandler.getHandler(); if (handler instanceof Controller) { ((Controller)handler).handleRequest(request, response); } else if (handler instanceof HttpRequestHandler) { ((HttpRequestHandler)handler).handleRequest(request, response); } else if (handler instanceof HandlerMethod) { ((HandlerMethod)handler).getMethod().invoke(...); } else if (handler instanceof HandlerFunction) { ((HandlerFunction)handler).handle(...); }
但是SpringMVC並不是這麼寫的,還是採用的適配模式,把不同種類的Handler適配成一個HandlerAdapter,後續再執行HandlerAdapter的handle()方法就能執行不同種類Hanlder對應的方法。
針對不同的Handler,會有不同的適配器:
- HttpRequestHandlerAdapter
- SimpleControllerHandlerAdapter
- RequestMappingHandlerAdapter
- HandlerFunctionAdapter
適配邏輯爲:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { for (HandlerAdapter adapter : this.handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }
傳入handler,遍歷上面四個Adapter,誰支持就返回誰,比如判斷的代碼依次爲:
public boolean supports(Object handler) { return (handler instanceof HttpRequestHandler); } public boolean supports(Object handler) { return (handler instanceof Controller); } public final boolean supports(Object handler) { return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler)); } public boolean supports(Object handler) { return handler instanceof HandlerFunction; }
根據Handler適配出了對應的HandlerAdapter後,就執行具體HandlerAdapter對象的handle()方法了,比如:
HttpRequestHandlerAdapter的handle():
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((HttpRequestHandler) handler).handleRequest(request, response); return null; }
SimpleControllerHandlerAdapter的handle():
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return ((Controller) handler).handleRequest(request, response); }
HandlerFunctionAdapter的handle():
HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler; serverResponse = handlerFunction.handle(serverRequest);
因爲這三個接收的直接就是Requeset對象,不用SpringMVC做額外的解析,所以比較簡單,比較複雜的是RequestMappingHandlerAdapter,它執行的是加了@RequestMapping的方法,而這種方法的寫法可以是多種多樣,SpringMVC需要根據方法的定義去解析Request對象,從請求中獲取出對應的數據然後傳遞給方法,並執行。
@RequestMapping方法參數解析
當SpringMVC接收到請求,並找到了對應的Method之後,就要執行該方法了,不過在執行之前需要根據方法定義的參數信息,從請求中獲取出對應的數據,然後將數據傳給方法並執行。
一個HttpServletRequest通常有:
- request parameter
- request attribute
- request session
- reqeust header
- reqeust body
比如如下幾個方法:
public String test(String username) { return "zhouyu"; }
表示要從request parameter中獲取key爲username的value
public String test(@RequestParam("uname") String username) { return "zhouyu"; }
表示要從request parameter中獲取key爲uname的value
public String test(@RequestAttribute String username) { return "zhouyu"; }
表示要從request attribute中獲取key爲username的value
public String test(@SessionAttribute String username) { return "zhouyu"; }
表示要從request session中獲取key爲username的value
public String test(@RequestHeader String username) { return "zhouyu"; }
表示要從request header中獲取key爲username的value
public String test(@RequestBody String username) { return "zhouyu"; }
表示獲取整個請求體
所以,我們發現SpringMVC要去解析方法參數,看該參數到底是要獲取請求中的哪些信息。
而這個過程,源碼中是通過HandlerMethodArgumentResolver來實現的,比如:
- 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; }
就是遍歷所有的HandlerMethodArgumentResolver,哪個能支持處理當前這個參數就由哪個處理。
比如:
@RequestMapping(method = RequestMethod.GET, path = "/test") @ResponseBody public String test(@RequestParam @SessionAttribute String username) { System.out.println(username); return "zhouyu"; }
以上代碼的username將對應RequestParam中的username,而不是session中的,因爲在源碼中RequestParamMethodArgumentResolver更靠前。
當然HandlerMethodArgumentResolver也會負責從request中獲取對應的數據,對應的是resolveArgument()方法。
比如RequestParamMethodArgumentResolver:
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); if (servletRequest != null) { Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) { return mpArg; } } Object arg = null; MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class); if (multipartRequest != null) { List<MultipartFile> files = multipartRequest.getFiles(name); if (!files.isEmpty()) { arg = (files.size() == 1 ? files.get(0) : files); } } if (arg == null) { String[] paramValues = request.getParameterValues(name); if (paramValues != null) { arg = (paramValues.length == 1 ? paramValues[0] : paramValues); } } return arg; }
核心是:
if (arg == null) { String[] paramValues = request.getParameterValues(name); if (paramValues != null) { arg = (paramValues.length == 1 ? paramValues[0] : paramValues); } }
按同樣的思路,可以找到方法中每個參數所要求的值,從而執行方法,得到方法的返回值。
@RequestMapping方法返回值解析
而方法返回值,也會分爲不同的情況。比如有沒有加@ResponseBody註解,如果方法返回一個String:
- 加了@ResponseBody註解:表示直接將這個String返回給瀏覽器
- 沒有加@ResponseBody註解:表示應該根據這個String找到對應的頁面,把頁面返回給瀏覽器
在SpringMVC中,會利用HandlerMethodReturnValueHandler來處理返回值:
- RequestResponseBodyMethodProcessor:處理加了@ResponseBody註解的情況
- ViewNameMethodReturnValueHandler:處理沒有加@ResponseBody註解並且返回值類型爲String的情況
- ModelMethodProcessor:處理返回值是Model類型的情況
- 還有很多其他的...
我們這裏只講RequestResponseBodyMethodProcessor,因爲它會處理加了@ResponseBody註解的情況,也是目前我們用得最多的情況。
RequestResponseBodyMethodProcessor相當於會把方法返回的對象直接響應給瀏覽器,如果返回的是一個字符串,那麼好說,直接把字符串響應給瀏覽器,那如果返回的是一個Map呢?是一個User對象呢?該怎麼把這些複雜對象響應給瀏覽器呢?
處理這塊,SpringMVC會利用HttpMessageConverter來處理,比如默認情況下,SpringMVC會有4個HttpMessageConverter:
- ByteArrayHttpMessageConverter:處理返回值爲字節數組的情況,把字節數組返回給瀏覽器
- StringHttpMessageConverter:處理返回值爲字符串的情況,把字符串按指定的編碼序列號後返回給瀏覽器
- SourceHttpMessageConverter:處理返回值爲XML對象的情況,比如把DOMSource對象返回給瀏覽器
- AllEncompassingFormHttpMessageConverter:處理返回值爲MultiValueMap對象的情況
StringHttpMessageConverter的源碼也比較簡單:
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException { HttpHeaders headers = outputMessage.getHeaders(); if (this.writeAcceptCharset && headers.get(HttpHeaders.ACCEPT_CHARSET) == null) { headers.setAcceptCharset(getAcceptedCharsets()); } Charset charset = getContentTypeCharset(headers.getContentType()); StreamUtils.copy(str, charset, outputMessage.getBody()); }
先看有沒有設置Content-Type,如果沒有設置則取默認的,默認爲ISO-8859-1,所以默認情況下返回中文會亂碼,可以通過以下來中方式來解決:
@RequestMapping(method = RequestMethod.GET, path = "/test", produces = {"application/json;charset=UTF-8"}) @ResponseBody public String test() { return "周瑜"; }
@ComponentScan("com.zhouyu") @Configuration @EnableWebMvc public class AppConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { StringHttpMessageConverter messageConverter = new StringHttpMessageConverter(); messageConverter.setDefaultCharset(StandardCharsets.UTF_8); converters.add(messageConverter); } }
不過以上四個Converter是不能處理Map對象或User對象的,所以如果返回的是Map或User對象,那麼得單獨配置一個Converter,比如MappingJackson2HttpMessageConverter,這個Converter比較強大,能把String、Map、User對象等等都能轉化成JSON格式。
@ComponentScan("com.zhouyu") @Configuration @EnableWebMvc public class AppConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter(); messageConverter.setDefaultCharset(StandardCharsets.UTF_8); converters.add(messageConverter); } }
具體轉化的邏輯就是Jackson2的轉化邏輯。
總結
以上就是整個SpringMVC從啓動到處理請求,從接收請求到執行方法的整體流程。
本系列文章來自圖靈學院周瑜老師分享,本博客整理學習並搬運