0%

SpringMVC 组件解析

介绍了 SpringMVC 整体结构中用到的九种组件。

HandlerMapping

HandlerMapping 的功能简单来说就是根据 url 查找对应的 handler。下图是 HandlerMapping 的继承结构图。

可以看到 HandlerMapping 家族的成员可以分为两支,一支继承 AbstractUrlHandlerMapping,另一支继承 AbstractHandlerMethodMapping,而这两分支都继承 AbstractHandlerMapping。

AbstractHandlerMethodMapping

AbstractHandlerMethodMapping 是常用 @RequestMapping 方法的 Handler。对于每个被操作的 handler method 有一个专门的类型 HandlerMethod 用来封装其 method 和 bean 的信息并提供访问。

// T 是代表匹配 Handler 的条件专用类。这里的条件不只是 url,还可以有很多其他条件,如 
// request 的类型(Get、Post等)、请求的参数、Header 等都可以作为匹配 HandlerMethod 的条件。
// 从 RequestMappingInfoHandlerMapping 的定义就可以看出默认使用 RequestMappingInfo。
public abstract class AbstractHandlerMethodMapping<T>

RequestCondition

RequestMappingInfo 实现了 RequestCondition 接口,此接口专门用于保存从 request 提取出的用于匹配 Handler 的条件。

在 AbstractRequestCondition 中,除了 CompositeRequestCondition 外每一个子类表示一种匹配条件。比如,PatternsRequestCondition 使用 url 做匹配,RequestMethodsRequestCondition 使用 RequestMethod 做匹配等。CompositeRequestCondition 本身并不实际做匹配,而是可以将多个别的 RequestCondition 封装到自己的一个变量里,在用的时候遍历封装 RequestCondition 的那个变量里所有的 RequestCondition 进行匹配,也就是大家所熟悉的责任链模式。

RequestCondition 的另一个实现就是这里要用的 RequestMappingInfo,它里面其实是用七个变量保存了七个 RequestCondition,在匹配时使用那七个变量进行匹配,这也就是可以在 @RequestMapping 中给处理器指定多种匹配方式的原因。

RequestMappingHandlerMapping

对于 HandlerMapping 来说,最常用的就是 RequestMappingHandlerMapping 实现类。下图为 DispatcherServlet 与 RequestMappingHandlerMapping 的调用流。

Mapping Relation

在 Map 映射的过程中,总共用到了以下三个 Map 映射关系:

  • handlerMethods:保存着匹配条件(也就是 RequestCondition)和 HandlerMethod 的对应关系。

  • urlMap:保存着 url 与匹配条件(也就是 RequestCondition)的对应关系,当然这里的 url 是 Pattern 式的,可以使用通配符。另外,这里使用的 Map 是 MultiValueMap,其一个 key 对应多个值。

  • nameMap:这个 Map 是 Spring MVC 4 新增的,它保存着 name 与 HandlerMethod 的对应关系,它使用的也是 MultiValueMap 类型的 Map,也就是说一个 name 可以有多个 HandlerMethod。这里的 name 是使用 HandlerMethodMappingNamingStrategy 策略的实现类从 HandlerMethod 中解析出来的,默认使用 RequestMappingInfoHandlerMethodMappingNamingStrategy 实现类,解析规则是:类名里的大写字母组合+“#”+方法名。这个 Map 在正常的匹配过程并不需要使用,它主要用在 MvcUriComponentsBuilder 里面,可以用来根据 name 获取相应的 url。用法如下:

HandlerAdapter

HandlerMapping 通过 request 找到了 handler, HandlerAdapter 是具体使用 Handler 来处理消息,每个 HandlerAdapter 封装了一种 Handler 的具体使用方法。HandlerAdapter 的继承结构如下图。

首先看一下 AbstractHandlerMethodAdapter,提供了 Handler 处理的模版方法,另外实现了 Order 接口,可以在配置时设置顺序。

public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered

而 RequestMappingHandlerAdapter 继承了 AbstractHandlerMethodAdapter,作为其实现类。

RequestMappingHandlerAdapter 的创建

RequestMappingHandlerAdapter 创建在 afterPropertiesSet 中实现,其内容主要是:argumentResolvers、initBinderArgumentResolvers、returnValueHandlers 以及 @ControllerAdvice 相关的 mоdеlАttrіbutеАdvісеСасhе、іnіtВіndеrАdvісеСасhе、rеѕроnѕеВоdуАdvісе 这六个属性。下面分别介绍:

  • argumentResolvers:用于给处理器方法和注释了 @ModelAttribute 的方法设置参数。
  • initBinderArgumentResolve:用于给注释了 @initBinder 的方法设置参数。
  • returnValueHandlers:用于将处理器的返回值处理成 ModelAndView 的类型。

  • modelAttributeAdviceCache 和 initBinderAdviceCache:分别用于缓存 @ControllerAdvice 注释的类里面注释了@ModelAttribute 和 @InitBinder 的方法,也就是全局的 @ModelAttribute 和 @InitBinder 方法。每个处理器自己的 @ModelAttribute 和 @InitBinder 方法是在第一次使用处理器处理请求时缓存起来的,这种做法既不需要启动时就花时间遍历每个 Controller 查找 @ModelAttribute 和 @InitBinder 方法,又能在调用过一次后再调用相同处理器处理请求时不需要再次查找而从缓存中获取。这两种缓存的思路类似于单例模式中的饿汉式和懒汉式。

  • responseBodyAdvice:用来保存前面介绍过的实现了 ResponseBodyAdvice 接口、可以修改返回的 ResponseBody 的类。
private void initControllerAdviceCache() {
   if (getApplicationContext() == null) {
      return;
   }

   // 获取到所有注释了 @ControllerAdvice 的 bean
   List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
   
   List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();

   for (ControllerAdviceBean adviceBean : adviceBeans) {
      Class<?> beanType = adviceBean.getBeanType();
      if (beanType == null) {
         throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
      }
      // 查找注释了 @ModelAttribute 而且没注释 @RequestMapping 的方法
      Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
      if (!attrMethods.isEmpty()) {
         this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
      }
     
      // 查找注释了 @InitBinder 的方法
      Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
      if (!binderMethods.isEmpty()) {
         this.initBinderAdviceCache.put(adviceBean, binderMethods);
      }
      // 查找实现了 RequestBodyAdvice 接口的类
      if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
         requestResponseBodyAdviceBeans.add(adviceBean);
      }
   }
   // 将查找到的实现了 RequestBodyAdvice 接口的类从前面添加到 requestResponseBodyAdviceBeans 属性
   if (!requestResponseBodyAdviceBeans.isEmpty()) {
      this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
   }
}

ResponseBodyAdvice 的实现类有两种注册方法,一种是直接注册到 RequestMappingHandlerAdapter,另外一种是通过 @ControllerAdvice 注释,让 SpringMVC 自己找到并注册。

初始化 ArgumentResolver

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
   List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

   // Annotation-based argument resolution
   // @RequestParam 和 @RequestPart
   resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
   // @MultiValueMap
   resolvers.add(new RequestParamMapMethodArgumentResolver());
   // @PathVariable
   resolvers.add(new PathVariableMethodArgumentResolver());
   // @PathVariable
   resolvers.add(new PathVariableMapMethodArgumentResolver());
   // @MatrixVariable
   resolvers.add(new MatrixVariableMethodArgumentResolver());
   // @MatrixVariable
   resolvers.add(new MatrixVariableMapMethodArgumentResolver());
   // @ModelAttribute
   resolvers.add(new ServletModelAttributeMethodProcessor(false));
   // @RequestBody
   resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
   // @RequestParam & @RequestPart
   resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
   // @RequestHeader
   resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
   // @RequestHeader
   resolvers.add(new RequestHeaderMapMethodArgumentResolver());
   // @CookieValue
   resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
   // @Value
   resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
   // @SessionAttribute
   resolvers.add(new SessionAttributeMethodArgumentResolver());
   // @RequestAttribute
   resolvers.add(new RequestAttributeMethodArgumentResolver());

   // Type-based argument resolution
   // 支持以下接口
   // WebRequest ServletRequest MultipartRequest 
   // HttpSession Principal InputStream Reader 
   // HttpMethod Locale TimeZone ZoneId 
   resolvers.add(new ServletRequestMethodArgumentResolver());
   // ServletResponse OutputStream Writer
   resolvers.add(new ServletResponseMethodArgumentResolver());
   // HttpEntity RequestEntity
   resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
   // RedirectAttributes
   resolvers.add(new RedirectAttributesMethodArgumentResolver());
   // Model
   resolvers.add(new ModelMethodProcessor());
   // Map
   resolvers.add(new MapMethodProcessor());
   // Errors
   resolvers.add(new ErrorsMethodArgumentResolver());
   // SessionStatus
   resolvers.add(new SessionStatusMethodArgumentResolver());
   // UriComponentsBuilder & ServletUriComponentsBuilder
   resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
   if (KotlinDetector.isKotlinPresent()) {
      resolvers.add(new ContinuationHandlerMethodArgumentResolver());
   }

   // Custom arguments
   if (getCustomArgumentResolvers() != null) {
      resolvers.addAll(getCustomArgumentResolvers());
   }

   // 最后两个解析器可以解析所有类型
   resolvers.add(new PrincipalMethodArgumentResolver());
   resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
   resolvers.add(new ServletModelAttributeMethodProcessor(true));

   return resolvers;
}

我们可以看到,自定义解析器优先级是最低的,如果自定义实现一个 @RequestParam 是不会生效的。

RequestMappingHandlerAdapter 的调用

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod 方法负责请求的处理,其主要使用了四个组件完成对请求的处理:WebDataBinderFactory、ModelFactory、ServletInvocableHandlerMethod 和 WebAsyncManager。

下面分别介绍一下上述四个组件的大致功能:

  • WebDataBinderFactory:request 与 method 参数绑定(工厂)
  • ModelFactory:建立 model(工厂),由于 JSP 没什么用,Model 模块略过。
  • ServletInvocableHandlerMethod:handler method 与 controller method 桥接
  • WebAsyncManager:Web 异步管理

WebDataBinder 具体用法见 《SpringMVC 参数绑定工具 DataBinder》

WebDataBinderFactory

WebDataBinderFactory 是用来创建 WebDataBinder 用于参数绑定的,主要功能就是实现参数跟 String 之间的类型转换。WebDataBinderFactory 的创建过程就是将 @InitBinder 的方法找出并注册到 WebDataBinderFactory。

@InitBinder 方法包括两部分:

  1. 全局 Controller 级别 InitBinder(即注释了 @ControllerAdvice)
  2. 单 Controller 级别 InitBinder

全局的 InitBinder 方法在创建 RequestMappingHandlerAdapter 的时候已经设置到缓存中了,而单 Controller 级别的 InitBinder 方法会在第一次调用后保存到缓存中。

ServletInvocableHandlerMethod

ServletInvocableHandlerMethod 继承自 HandlerMethod,实际请求的处理就是通过它来执行参数绑定、处理请求以及返回值处理。

HandlerMethodArgumentResolver

HandlerMethodArgumentResolver 的实现类是参数解析器。

HandlerMethodReturnValueHandler

HandlerMethodReturnValueHandler 用在 ServletInvocableHandlerMethod 中,作用是处理处理器执行后的返回值,主要有三个功能:

  1. 将相应参数添加到 Model
  2. 设置 View
  3. 如果请求已经处理完则设置 ModelAndViewContainer 的 requestHandled 为 true

HandlerExceptionResolver

其中 HandlerExceptionResolverComposite 作为容器使用,可以封装别的 Resolver。

HandlerExceptionResolver 的主要实现都继承自抽象类 AbstractHandlerExceptionResolver。主要实现类有以下四种:

  • DefaultHandlerExceptionResolver:按不同类型分别对异常进行解析
  • ResponseStatusExceptionResolver:解析有 @ResponseStatus 注释类型的异常
  • SimpleMappingExceptionResolver:通过配置的异常类和 View 的对应关系来解析异常

MultipartResolver

MultipartResolver 用于处理上传请求,有两个实现类:StandardServletMultipartResolver 和 CommonsMultipartResolver。前者使用了 Servlet3.0 标准的上传方式,后者使用了 Apache commons-fileupload。它们所对应的 Request 分别为 StandardMultipartHttpServletRequest 和 DefaultMultipartHttpServletRequest 类型。

LocaleResolver

LocaleResolver 的作用是使用 request 解析出 Locale,它的继承结构如下图所示。

虽然 LocaleResolver 的实现类结构看起来比较复杂,但是实现却非常简单。在 LocaleResolver 的实现类中,AcceptHeaderLocaleResolver 直接使用了Header 里的 Accept-Language 值,不可以在程序中修改;FixedLocaleResolver 用于解析出固定的 Locale,也就是说在创建时就设置好确定的 Locale,之后无法修改;SessionLocaleResolver 用于将 Locale 保存到Session中,可以修改;CookieLocaleResolver 用于将 Locale 保存到 Cookie 中,可以修改。

另外,从 Spring MVC 4.0 开始,LocaleResolver 添加了一个子接口 LocaleContextResolver,其中增加了获取和设置 LocaleContext 的能力,并添加了抽象类 AbstractLocaleContextResolver。抽象类添加了对 TimeZone 也就是时区的支持。

ViewResolver

ViewResolver 主要的作用是根据视图名和 Locale 解析出视图,解析过程主要做两件事:解析出使用的模板和视图的类型。

RequestToViewNameTranslator

RequestToViewNameTranslator 可以在处理器返回的 View 为空时使用它根据 Request 获取 viewName。

ThemeResolver

ThemeResolver 用于根据 request 解析 Theme。

FlashMapManager

FlashMapManager 用来管理 FlashMap,FlashMap 用于在 redirect 时传递参数。