赞
踩
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的过程中:
而在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对象:
- @Component("/test2")
- public class ControllerController implements Controller {
-
- @Override
- public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
- ModelAndView mav = new ModelAndView(new MappingJackson2JsonView());
- mav.addObject("result", "{}");
- return mav;
- }
- }
实现了HttpRequestHandler接口的Bean对象:
- @Component("/test3")
- public class HttpRequestHandlerController implements HttpRequestHandler {
-
- @Override
- public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- JSONObject jsonObject = new JSONObject();
- jsonObject.put("result", "{}");
- response.getWriter().write(jsonObject.toJSONString());
- }
- }
添加了@RequestMapping注解的方法:
- public class Controller {
-
- @RequestMapping(method = RequestMethod.GET, path = "/test")
- @ResponseBody
- public Object controller() {
- Map<Object, String> result = new HashMap<>(1);
- result.put("result", "{}");
- return result;
- }
-
- }
一个HandlerFunction对象(以下代码中有两个):
- public class AppConfig {
-
- @Bean
- public RouterFunction<ServerResponse> person() {
- JSONObject jsonObject = new JSONObject();
- jsonObject.put("result", "{}");
- return route()
- .GET("test4", request -> ServerResponse.status(HttpStatus.OK).body(jsonObject.toJSONString()))
- .POST("test4", request -> ServerResponse.status(HttpStatus.OK).body(jsonObject.toJSONString()))
- .build();
- }
-
- }
什么是HandlerMapping?
HandlerMapping负责去寻找Handler,并且保存路径和Handler之间的映射关系。
因为有不同类型的Handler,所以在SpringMVC中会由不同的HandlerMapping来负责寻找Handler,比如:
BeanNameUrlHandlerMapping的寻找流程:
RequestMappingHandlerMapping的寻找流程:
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呢?看源码:
很简单,就是遍历,找到就返回,默认顺序为:
所以BeanNameUrlHandlerMapping的优先级最高,比如:
如果请求路径都是/test,但是最终是Controller接口的会生效。
什么是HandlerAdapter?
找到了Handler之后,接下来就该去执行了,比如执行下面这个test()
但是由于有不同种类的Handler,所以执行方式是不一样的,再来总结一下Handler的类型:
所以,按逻辑来说,找到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,会有不同的适配器:
适配逻辑为:
- 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通常有:
比如如下几个方法:
- public String test(String username) {
- return "test";
- }
表示要从request parameter中获取key为username的value
- public String test(@RequestParam("uname") String username) {
- return "test";
- }
表示要从request attribute中获取key为username的value
- public String test(@RequestAttribute String username) {
- return "test";
- }
表示要从request session中获取key为username的value
- public String test(@SessionAttribute String username) {
- return "test";
- }
表示要从request header中获取key为username的value
- public String test(@RequestHeader String username) {
- return "test";
- }
表示获取整个请求体
- public String test(@RequestBody String username) {
- return "test";
- }
所以,我们发现SpringMVC要去解析方法参数,看该参数到底是要获取请求中的哪些信息。
而这个过程,源码中是通过HandlerMethodArgumentResolver来实现的,比如:
而在判断某个参数该由哪个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 "test";
- }
以上代码的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:
在SpringMVC中,会利用HandlerMethodReturnValueHandler来处理返回值:
我们这里只讲RequestResponseBodyMethodProcessor,因为它会处理加了@ResponseBody注解的情况,也是目前我们用得最多的情况。
RequestResponseBodyMethodProcessor相当于会把方法返回的对象直接响应给浏览器,如果返回的是一个字符串,那么好说,直接把字符串响应给浏览器,那如果返回的是一个Map呢?是一个User对象呢?该怎么把这些复杂对象响应给浏览器呢?
处理这块,SpringMVC会利用HttpMessageConverter来处理,比如默认情况下,SpringMVC会有4个HttpMessageConverter:
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 "test";
- }
- @ComponentScan
- @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
- @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的转化逻辑。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。