【Spring MVC】MVC请求的处理过程一览-RequestMappingHandlerAdapter、ModelAndViewContainer
1 前言
上节我们看了 MVC 请求的处理过程【Spring MVC】MVC请求的处理过程一览,这节我们主要看看常用的一些请求参数注解的解析过程,方便我们能更好的对参数做一些增强处理。
我们平时写的一些 controller,里边的每个方法带有 @RequestMapping 的注解的,都会解析成 HandlerMethod 对象,并都注册到了AbstractHandlerMethodMapping 类的内部类 MappingRegistry 里,然后当请求进来以后,匹配到某个 HandlerMethod,再用 HandlerExecutionChain 执行器链对象包起来,添加一些能匹配当前请求的拦截器即可,然后找到一个执行的适配器,HandlerMethod 对象的执行器是 RequestMappingHandlerAdapter,执行的主要逻辑是先用 ServletInvocableHandlerMethod 包装一下 HandlerMethod,接着执行它的invokeAndHandle,在看 ServletInvocableHandlerMethod 之前,RequestMappingHandlerAdapter 的处理主流程以及其中出现的几个组件比如 ModelAndViewContainer 方便后续更好的理解参数的解析。
2 RequestMappingHandlerAdapter
2.1 概述
HandlerMapping 通过 request 找到了 Handler,HandlerAdapter 是具体使用Handler 来干活的,每个 HandlerAdapter 封装了一种 Handler 的具体使用方法,我们先看下HandlerAdapter 的继承结构:

HandlerAdapter 的接口,里面一共有三个方法,一个用来判断是否支持传入的Handler,一个用来使用Handler 处理请求,还有一个用来获取资源的Last-Modifed 值。
可以看到HandlerAdapter 的结构非常简单,一共有4类 Adapter,其中只有 RequestMappingHandlerAdapter 有两层,别的都是只有一层,也就是直接实现的 HandlerAdapter 接口。
在这四类 Adapter 中 RequestMappingHandlerAdapter 的实现非常复杂,而其他三个则非常简单,因为其他三个 Handler 的格式都是固定的,只需要调用固定的方法就可以了,但是 RequestMappingHandlerAdapter 所处理的 Handler 可以是任意的方法,没有任何约束,这就极大地增加了难度。其实调用并不算难,大家应该都可以想到,使用反射就可以了,关键是参数值的解析,参数可能有各种各样的类型,而且有几个参数也不确定。
HttpRequestHandlerAdapter、SimpleServletHandlerAdapter 和 SimpleControllerHandler-Adapter 分别适配 HttpRequestHandler、Servlet 和 Controller 类型的Handler,方法非常简单,都是调用 Handler 里固定的方法,代码如下:
// 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); } // SimpleServletHandlerAdapter#handle public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((Servlet) handler).service(request, response); return null; }
这三个 Adapter 非常简单,这里就不多说了,我们主要看看 RequestMappingHandlerAdapter。
RequestMappingHandlerAdapter继承⾃AbstractHandlerMethodAdapter,后者⾮常简单,三个接⼜⽅法分别调⽤了三个模板⽅法supportsInternal、handleInternal和getLastModifiedInternal,在supports⽅法中除了supportsInternal还增加了个条件—Handler必须是HandlerMethod类型。另外实现了 Order接口,可以在配置时设置顺序。
RequestMappingHandlerAdapter 可以说是整个Spring MVC 中最复杂的组件。它的 supportsInternal 直接返回 true,也就是不增加判断 Handler的条件,只需要满足父类中的 HandlerMethod 类型的要求就可以了;getLastModifiedInternal 直接返回-1;最重要的就是 handleInternal 方法,就是这个方法实际使用Handler 处理请求。
具体处理过程大致可以分三步:
(1)备好处理器所需要的参数。
(2)使用处理器处理请求。
(3)处理返回值,也就是将不同类型的返回值统一处理成 ModelAndView 类型。
这三步里面第2步是最简单的,直接使用反射技术调用处理器执行就可以了,第3步也还算简单,最麻烦的是第1步,也就是参数的准备工作,这一步根据处理器的需要设置参数,而参数的类型、个数都是不确定的,所以难度非常大,另外这个过程中使用了大量的组件,这也是这一步的代码不容易理解的重要原因之一。
参数来源有6个:
(1)request 中相关的参数,主要包括 url 中的参数、post 过来的参数以及请求头所包含的值。
(2)cookie 中的参数。
(3)session 中的参数。
(4)设置到 FlashMap.中的参数,这种参数主要用于 redirect 的参数传递。
(5)SessionAttributes 传递的参数,这类参数通过@SessionAttributes 注释传递
(6)通过相应的注释了 @ModelAttribute 的方法进行设置的参数。
参数具体解析是使用 HandlerMethodArgumentResolver 类型的组件完成的,不同类型的参数使用不同的 ArgumentResolver 来解析。有的 Resolver 内部使用了 WebDataBinder,可以通过注释了 @InitBinder 的方法来初始化。注释了 @InitBinder 的方法也需要绑定参数,而且也是不确定的,所以 @InitBinder 注释的方法本身也需要 ArgumentResolver 来解析参数,但它使用的和 Handler 使用的不是同一套 ArgumentResolver。另外,注释了 @ModelAttribute 的方法也需要绑定参数,它使用的和 Handler 使用的是同一套 ArgumentResolver。
简单了解了RequestMappingHandlerAdapter后,接下来我们看看他的创建或者初始化。
2.2 创建
RequestMappingHandlerAdapter 的创建在 afterPropertiesSet 方法中实现,其内容主要是初始化了 argumentResolvers、initBinderArgumentResolvers、returnValueHandlers 以及 @ControllerAdvice注释的类相关的 modelAttributeAdviceCache、initBinderAdviceCache 和 responseBodyAdvice 这 6 个属性,下面是这6个属性的解释:
(1)argumentResolvers:用于给处理器方法和注释了@ModelAttribute 的方法设置参数。
(2)initBinderArgumentResolvers:用于给注释了 @initBinder 的方法设置参数。
(3)returnValueHandlers: 用于将处理器的返回值处理成 ModelAndView 的类型。
(4)modelAttributeAdviceCache 和 initBinderAdviceCache:分别用于缓存 @ControllerAdvice注释的类里面注释了 @ModelAttribute 和 @InitBinder 的方法,也就是全局的@ModelAttribute 和@InitBinder 方法。每个处理器自己的 @ModelAttribute 和 @InitBinder 方法是在第一次使用处理器处理请求时缓存起来的,这种做法既不需要启动时就花时间遍历每个 Controller 查找 @ModelAttribute 和 @InitBinder 方法,又能在调用过一次后再调用相同处理器处理请求时不需要再次查找而从缓存中获取。这两种缓存的思路类似于单例模式中的饿汉式和懒汉式。
(5)responseBodyAdvice:用来保存前面介绍过的实现了 ResponseBodyAdvice 接口、可以修改返回的 ResponseBody 的类
把这些都弄明白,再分析 RequestMappingHandlerAdapter 就容易多了,需要注意的是,这些属性都是复数形式,也就是可以有多个,在使用的时候是按顺序调用的,所以这些属性初始化时的添加顺序就非常重要了要留意一下。afterPropertiesSet 的代码如下:
// RequestMappingHandlerAdapter#afterPropertiesSet() public void afterPropertiesSet() { // 初始化注释了 @ControllerAdvice 的类的相关属性 // Do this first, it may add ResponseBody advice beans initControllerAdviceCache(); if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.initBinderArgumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers(); this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.returnValueHandlers == null) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } }
非常清晰的4步骤,首先 initControllerAdviceCache 初始化注释了 @ControllerAdvice 的类的那三个属性,然后依次初始化 argumentResolvers、initBinderArgumentResolvers 和 returValueHandlers。后面三个属性初始化的方式都一样,都是先调用getDefaultXXX 得到相应的值,然后设置给对应的属性,而且都是 new 出来的 XXXComposite 类型,这种类型在分析 HandlerMapping 中的 RequestCondition 时已经见到过了,使用的是责任链模式,它自己并不实际干活,而是封装了多个别的组件,干活时交给别的组件,主要作用是方便调用。getDefaultXXX 方法稍后分析,下面先来看一下 initControllerAdviceCache 是怎么工作的:
// RequestMappingHandlerAdapter#initControllerAdviceCache() 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); } // 如果这个 bean 实现了 ResponseBodyAdvice 把它放到当前的集合里,主要为了下边的添加到集合的头部 if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) { requestResponseBodyAdviceBeans.add(adviceBean); } } // 放到头部 if (!requestResponseBodyAdviceBeans.isEmpty()) { this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans); } // 日志打印略过 }
这里首先通过 ControllerAdviceBean.fndAnnotatedBeans(getApplicationContext())拿到容器中所有注释了 @ControllerAdvice 的bean,并根据 Order 排了序,然后使用for 循环遍历,找到每个 bean 里相应的方法(或bean 自身)设置到相应的属性。查找 @ModelAttribute 和 @InitBinder 注解方法使用的是 HandlerMethodSelector.selectMethods,静态变量 INITBINDER_METHODS 和MODEL_ATTRIBUTE_METHODS,它们分别表示查找注释了@InitBinder 的方法和注释了 @ModelAttribute 而且没注释 @RequestMapping 的方法(同时注释了@RequestMapping 的方法只是将返回值设置到 Model 而不是作为View 使用了,但不会提前执行)。
实现了 ResponseBodyAdvice 接口的类并没有在for 循环里直接添加到 responseBodyAdvice 属性中,而是先将它们保存到 responseBodyAdviceBeans 临时变量里,最后再添加到 responseBodyAdvice 里的,添加的代码是this.responseBodyAdvice.addAII(0, responseBodyAdviceBeans),这么做的目的就是要把这里找到的 ResponseBodyAdvice 放在最前面。ResponseBodyAdvice 的实现类有两种注册方法,一种是直接注册到 RequestMappingHandlerAdapter,另外一种是通过@ControllerAdvice 注释,让 Spring MVC 自己找到并注册,从这里可以看到通过 @ControllerAdvice 注释注册的优先级更高。
说完 initControllerAdviceCache,再返回去看一下那三个 getDefaultXXX方法,这三个方法非常类似,下面以 getDefaultArgumentResolvers 为例来进行分析,这个方法用来设置argumentResolvers 属性,这是一个非常核心的属性,后面要分析的很多组件都和这个属性有关系。代码如下:
// RequestMappingHandlerAdapter#getDefaultArgumentResolvers() private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30); // 添加一些注解解析参数的解析器 // Annotation-based argument resolution // @RequestParam resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); // @PathVariable resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); // @ModelAttribute resolvers.add(new ServletModelAttributeMethodProcessor(false)); // @RequestBody resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); resolvers.add(new RequestHeaderMapMethodArgumentResolver()); resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory())); resolvers.add(new SessionAttributeMethodArgumentResolver()); resolvers.add(new RequestAttributeMethodArgumentResolver()); // 添加按类型解析参数的解析器 // Type-based argument resolution resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice)); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); resolvers.add(new MapMethodProcessor()); resolvers.add(new ErrorsMethodArgumentResolver()); resolvers.add(new SessionStatusMethodArgumentResolver()); resolvers.add(new UriComponentsBuilderMethodArgumentResolver()); if (KotlinDetector.isKotlinPresent()) { resolvers.add(new ContinuationHandlerMethodArgumentResolver()); } // 添加一些自定义的参数解析器,主要用于解析自定义类型的 // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // 可以解析所有参数类型的解析器 // Catch-all resolvers.add(new PrincipalMethodArgumentResolver()); resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; }
通过注释可以看到,这里的解析器可以分为四类:通过注释解析的解析器、通过类型解析的解析器、自定义的解析器和可以解析所有类型的解析器。第三类是可以自己定义的解析器,定义方法是自己按要求写个 resolver 然后通过 customArgumentResolvers 属性注册到 RequestMappingHandlerAdapter。需要注意的是,自定义的解析器是在前两种类型的解析器都无法解析的时候才会使用到,这个顺序无法改变!所以如果要想自己写一个解析器来解析 @PathVariable 注释的PathVariable类型的参数,是无法实现的,即使写出来并注册到RequestMappingHandlerAdapter 上面也不会被调用。Spring MVC自己定义的解析器的顺序也是固定的,不可以改变。
2.3 处理请求
RequestMappingHandlerAdapter 处理请求的入口方法是 handleInteral,代码如下:
// RequestMappingHandlerAdapter#handleInternal() protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; checkRequest(request); // Execute invokeHandlerMethod in synchronized block if required. if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No synchronization on session demanded at all... // 其他的我们不必太关注,最关键其实只有这一行代码,执行请求的处理 mav = invokeHandlerMethod(request, response, handlerMethod); } if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } } return mav; }
invokeHandlerMethod 方法非常重要,他具体执行请求的处理,代码如下:
// RequestMappingHandlerAdapter#invokeHandlerMethod() protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { // 创建一个ServletWebRequest对象,将request和response封装成ServletWebRequest对象 ServletWebRequest webRequest = new ServletWebRequest(request, response); try { // 定义和初始化了三个类型 WebDataBinderFactory ModelFactory ServletInvocableHandlerMethod WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, invocableMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); asyncWebRequest.setTimeout(this.asyncRequestTimeout); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.setTaskExecutor(this.taskExecutor); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.registerCallableInterceptors(this.callableInterceptors); asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); if (asyncManager.hasConcurrentResult()) { Object result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; asyncManager.clearConcurrentResult(); LogFormatUtils.traceDebug(logger, traceOn -> { String formatted = LogFormatUtils.formatValue(result, !traceOn); return "Resume with async result [" + formatted + "]"; }); invocableMethod = invocableMethod.wrapConcurrentResult(result); } // 执行请求处理、解析参数、执行方法、封装返回 invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } // 处理完请求后的后置处理比如更新 model,根据 mavContainer 创建 ModelAndView等 return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } }
在 invokeHandleMethod 方法中首先使用 request 和 response 创建了 ServletWebRequest 类型的 webRequest, 在 ArgumentResolver 解析参数时使用的 request 就是这个 webRequest,当然如果我们的处理器需要 HttpServletRequest 类型的参数,ArgumentResolver 会给我们设置原始的 request。
接着对 WebDataBinderFactory、ModelFactory、ServletinvocableHandlerMethod 这三个类型的变量进行了定义和初始化,接下来我们看下这三个类型。
2.3.1 WebDataBinderFactory
WebDataBinderFactory 它的作用从名字就可以看出是用来创建 WebDataBinder 的,WebDataBinder用于参数绑定,主要功能就是实现参数跟String之间的类型转换,ArgumentResolver 在进行参数解析的过程中会用到 WebDataBinder,另外 ModeIFactory 在更新Model 时也会用到它。
WebDataBinderFactory 的创建过程就是将符合条件的注释了 @InitBinder 的方法找出来,并使用它们新建出 ServletRequestDataBinderFactory 类型的 WebDataBinderFactory。这里的InitBinder 方法包括两部分:一部分是注释了@ControllerAdvice 的并且符合要求的全局处理器里面的 InitBinder 方法;第二部分就是处理器自身的InitBinder 方法,添加的顺序是先添加全局的后添加自身的。第二类 InitBinder 方法会在第一次调用后保存到缓存中,以后直接从缓存获取就可以了。查找注释了 @InitBinder 方法的方法和以前一样,使用 HandlerMethodSelector.selectMethods 来找,而全局的 InitBinder 方法在创建 RequestMappingHandlerAdapter 的时候已经设置到缓存中了。WebDataBinderFactory 创建代码如下:
// RequestMappingHandlerAdapter#getDataBinderFactory() private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception { // 我们的 bean class 比如 DemoController.class Class<?> handlerType = handlerMethod.getBeanType(); Set<Method> methods = this.initBinderCache.get(handlerType); if (methods == null) { // 获取这个 bean 里带有 @InitBinder 注解的方法,并缓存起来 methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS); this.initBinderCache.put(handlerType, methods); } List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>(); // 首先添加全局的 @InitBinder 方法 // Global methods first this.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> { if (controllerAdviceBean.isApplicableToBeanType(handlerType)) { Object bean = controllerAdviceBean.resolveBean(); for (Method method : methodSet) { initBinderMethods.add(createInitBinderMethod(bean, method)); } } }); // 其次再添加当前自己的 for (Method method : methods) { Object bean = handlerMethod.getBean(); initBinderMethods.add(createInitBinderMethod(bean, method)); } return createDataBinderFactory(initBinderMethods); } protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods) throws Exception { return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer()); }
通过注释大家应该就很容易理解了。需要注意的是 HandlerMethodSelector.selectMethods的返回值是一个 Set,如果没找到相应的方法会返回一个空 Set 而不是null,所以即使没找到 InitBinder 方法,initBinderCache 也会为当前的Handler 设置一个空 Set,这样就可以用 initBinderCache.get(handlerType)是否 null 区分开没有调用过的处理器和调用过但没有InitBinder 方法的处理器。WebDataBinderFactory 主要和相应的 InitBinder 方法相关联。
其实说实话这段的处理跟我们平时用处不大,我经历过的项目中,代码中很少看见过 @InitBinder,哈哈哈。
2.3.2 ModelFactory
ModelFactory 是用来处理Model 的,主要包含两个功能:①在处理器具体处理之前对Model 进行初始化;②在处理完请求后对 Model 参数进行更新。
给Model 初始化具体包括三部分内容:①将原来的 SessionAttributes 中的值设置到Model; ②执行相应注释了@ModelAttribute 的方法并将其值设置到 Mode;③处理器中注释了@ModelAttribute 的参数如果同时在 SessionAttributes 中也配置了,而且在 mavContainer 中还没有值则从全部 SessionAttributes(可能是其他处理器设置的值)中查找出并设置进去。
对Model 更新是先对 SessionAttributes 进行设置,设置规则是如果处理器里调用了SessionStatus#setComplete 则 将 SessionAttributes 清空,否则将 mavContainer 的 defaultModel(可以理解Model,后面 ModelAndViewContainer 中会详细讲解)中相应的参数设置到SessionAttributes 中,然后按需要给 Model 设置参数对应的 BindingResult。
从这里可以看出调用 SessionStatus#setComplete 清空 SessionAttributes 是在整个处理执行完以后才执行的,也就是说这条语句在处理器中的位置并不重要,放在处理器的开头或者结尾都不会影响当前处理器对 SessionAttributes 的使用。
ModelFactory 的创建过程在 getModelFactory 方法中,代码如下:
// RequestMappingHandlerAdapter#getModelFactory() private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) { // 获取 SessionAttributesHandler SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod); Class<?> handlerType = handlerMethod.getBeanType(); // 获取处理器类的类型 Set<Method> methods = this.modelAttributeCache.get(handlerType); if (methods == null) { // 获取带 @ModelAttribute 注解的并且不带 @RequestMapping 注解的方法并缓存起来 methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS); this.modelAttributeCache.put(handlerType, methods); } // 一样先添加全局的再添加自己的 List<InvocableHandlerMethod> attrMethods = new ArrayList<>(); // Global methods first this.modelAttributeAdviceCache.forEach((controllerAdviceBean, methodSet) -> { if (controllerAdviceBean.isApplicableToBeanType(handlerType)) { Object bean = controllerAdviceBean.resolveBean(); for (Method method : methodSet) { attrMethods.add(createModelAttributeMethod(binderFactory, bean, method)); } } }); for (Method method : methods) { Object bean = handlerMethod.getBean(); attrMethods.add(createModelAttributeMethod(binderFactory, bean, method)); } return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler); } public ModelFactory(@Nullable List<InvocableHandlerMethod> handlerMethods, WebDataBinderFactory binderFactory, SessionAttributesHandler attributeHandler) { if (handlerMethods != null) { for (InvocableHandlerMethod handlerMethod : handlerMethods) { this.modelMethods.add(new ModelMethod(handlerMethod)); } } this.dataBinderFactory = binderFactory; this.sessionAttributesHandler = attributeHandler; }
从最后一句新建 ModelFactory 中可以看出主要使用了三个参数,第一个是注释了@ModelAttribute 的方法,第二个是 WebDataBinderFactory,第三个是 SessionAttributesHandler。
其中 WebDataBinderFactory 使用的就是上面创建出来的 WebDataBinderFactory;Session-AttributesHandler 的创建方法getSessionAttributesHandler 在前面已经介绍过了;注释了@ModelAttribute 的方法分两部分:一部分是注释了@ControllerAdvice的类中定义的全局的@ModelAttribute 方法;另一部分是当前处理器自身的@ModelAttribute 方法,添加规则是先添加全局的后添加自己的。
2.3.3 ServletInvocableHandlerMethod
ServletlnvocableHandlerMethod 类型非常重要,它继承自 HandlerMethod,并且可以直接执行。实际请求的处理就是通过它来执行的,参数绑定、处理请求以及返回值处理都在它里边完成,创建方法 createRequestMappingMethod 代码如下:
// 创建 ServletInvocableHandlerMethod ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); // 赋值参数解析器、结果解析器 if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
这里首先使用 handlerMethod 新建类 ServletInvocableHandlerMethod类,然后将 argumentResolvers、return ValueHandlers、 binderFactory 和 parameterNameDiscoverer设置进去就完成了。
这三个变量弄明白后 invokeHandleMethod 方法就容易理解了。这三个变量创建完之后的工作还有三步(这里省略了异步处理):①新建传递参数的ModelAndViewContainer 容器,并将相应参数设置到其 Model 中;②执行请求;③请求处理完后进行一些后置处理。
新建 ModelAndViewContainer 类型的 mavContainer 参数,用于保存Model 和 View,它贯穿于整个处理过程(注意,在处理请求时使用的并不是ModelAndView),然后对 mavContainer 进行了设置,主要包括三部分内容:①将 FlashMap 中的数据设置到Model ;②使用 modeIFactory 将 SessionAttributes 和注释了 @ModelAttribute 的方法的参数设置到 Model;③根据配置对 ignoreDefaultModelOnRedirect 进行了设置,这个参数在分析 ModelAndViewContainer 的时候再详细讲解。设置代码如下:
// RequestMappingHandlerAdapter#invokeHandlerMethod() ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest, mavContainer, invocableMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
到这里传递参数的容器就准备好了。设置完 mavContainer 后又做了一些异步处理的相关的工作。
执行请求,具体方法是直接调用 ServletlnvocableHandlerMethod 里的 invokeAndHandle方法执行的,代码如下:
// RequestMappingHandlerAdapter#invokeHandlerMethod() // 执行请求处理、解析参数、执行方法、封装返回 invocableMethod.invokeAndHandle(webRequest, mavContainer);
ServletInvocableHandlerMethod 的 invokeAndHandle 方法执行请求的具体过程等下我们细看这个方法。
处理完请求后的后置处理,这是在getModelAndView 方法中处理的。一共做了三件事:①调用ModeIFactory的updateModel 方法更新了Model(包括设置了SessionAttributes 和 给 Model 设置 BindingResult);② 根据 mavContainer 创建了ModelAndView ;③如果 mavContainer 里的model 是RedirectAttributes 类型,则将其值设置到FlashMap。代码如下:
// RequestMappingHandlerAdapter#getModelAndView() private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception { modelFactory.updateModel(webRequest, mavContainer); if (mavContainer.isRequestHandled()) { return null; } ModelMap model = mavContainer.getModel(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus()); // 如果mavContainer里的view不是引用,也就是不是string类型,则设置到mav中 if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); if (request != null) { RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } } return mav; }
这里的model只有处理器在返回redirect类型的视图时才可能是 RedirectAttributes类型,否则不会是RedirectAttributes 类型,也就是说在不返回 redirect 类型视图的处理器中即使使用RedirectAttributes 设置了变量也不会保存到FalseMap中,具体细节介绍ModelAndViewContainer 的时候再详细分析。
在了解大概的一个RequestMappingHandlerAdapter 自身的结构。作为一个 HandlerAdapter,RequestMappingHandlerAdapter 的作用就是使用处理器处理请求。它使用的处理器是HandlerMethod 类型,处理请求的过程主要分为三步:绑定参数、执行请求和处理返回值。
所绑定的参数的来源有6个地方:① request 中相关的参数,主要包括 url 中的参数、post过来的参数以及请求头中的值;②cookie 中的参数;③session 中的参数;④设置到 FlashMap中的参数,这种主要用于 redirect 的参数传递;⑤ SessionAttributes 传递的参数;⑥通过相应的注释了@ModelAttribute 的方法进行设置的参数。前三类参数通过 request 管理,后三类通过Model管理。前三类在 request 中获取,不需要做过多准备工作。第四类参数是直接在RequestMappingHandlerAdapter 中进行处理的,在请求前将之前保存的设置到 model,请求处理完成后如果需要则将 model 中的值设置到 FlashMap。后两类参数使用ModelFactory 管理。
准备好参数源后使用 ServletinvocableHlandlerMethod 组件具体执行请求,其中包括使用ArgumentResolver 解析参数、执行请求、使用 ReturnValueHandler 处理返回值等内容,详细内容会在 ServletlnvocableHandlerMethod 中具体讲解。
ServletinvocableHiandlerMethod 执行完请求处理后还有一些扫尾工作,主要是Model 中参数的缓存和 ModelAndView 的创建。
整个处理过程其实非常简单,只是里面使用了很多我们不熟悉的组件,这些组件如果理解了再返回来看就简单了。接下来就再看一个 ModelAndViewContainer。
3 ModelAndViewContainer
ModelAndViewContainer 承担着整个请求过程中数据的传递工作。它除了保存Model 和View 外还有一些别的功能,如果不知道这些功能,很多代码就无法理解。先看一下它里面所包含的属性,定义如下:
// ModelAndViewContainer // 视图,可以是实际视图也可以是String类型的逻辑视图 private Object view: // 默认使用的 model private final ModelMap defaultModel = new BindingAwareMode1Map(); // redirect 类型的 model private ModelMap redirectModel; // 用于设置 SessionAttribute 使用完的标志 private final SessionStatus sessionStatus = new SimpleSessionStatus (); // 如果为 true 则在处理器返回 redirect 视图时一定不使用 defaultModel private boolean ignoreDefaultModelOnRedirect = false: // 处理器返回 redirect 视图的标志 private boolean redirectModelScenario = falsei // 请求是否已经处理完成的标志 private boolean requestHandled = false;
先看一下 defaultModel 和 redirectModel,这两个都是Model,前者是默认使用的Model,后者用于传递redirect 时的参数。我们在处理器中使用了 Model 或者 ModeIMap时 ArgumentResolver会传入 defaultModel,它是BindingAwareModeIMap类型,既继承了 ModeIMap 又实现了Model 接口,所以在处理器中使用Model 或者ModeIMap 其实使用的是同一个对象,Map 参数传人的也是这个对象。处理器中 RedirectAttributes 类型的参数 ArgumentResolver 会传入 redirectModel,它实际上是 RedirectAttributesModeIMap类型。ModelAndViewContainer 的 getModel 方法会根据条件返回这两个 Model 里的一个,代码如下:
// ModelAndViewContainer public ModelMap getModel() { if (useDefaultModel()) { return this.defaultModel; } else { if (this.redirectModel == null) { this.redirectModel = new ModelMap(); } return this.redirectModel; } } private boolean useDefaultModel() { return (!this.redirectModelScenario || (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect)); }
判断逻辑在 useDefaultModel 中,如果 redirectModeIScenario 为 false,也就是返回的不是redirect 视图的时候一定返回 defaultModel,如果返回 redirect 视图的情况下需要根据redirectModel 和 ignoreDefaultModelOnRedirect 的情况进一步,如果 redirectModel 不为空和ignoreDefaultModelOnRedirect 设置 true 这两个条件中有一个成立则返回 redirectModel,否则返回 defaultModel。总结如下:
返回 defaultModel 的情况:①处理器返回的不是 redirect 视图;②处理器返回的是 redirect视图但是 redirectModel null,而且 ignoreDefaultModelOnRedirect 也是 false。
返回 redirectModel 的情况:①处理器返回 redirect 视图,并且 redirectModel null;②处理器返回的是 redirect 视图,并且 ignoreDefaultModelOnRedirect 为 true。
ignoreDefaultModelOnRedirect 可以在 RequestMappingHandlerAdapter 中设置。判断处理器返回的是不是 redirect 视图的标志设置在 redirectModeIScenario 中,它是在 Return ValueHandler中设置的,ReturnValueHandler 如果判断到是 redirect 视图就会将 redirectModeIScenario 设置力true。也就是说在 ReturnValueHandler 处理前 ModelAndView Container 的getModel 返回的一定是 defaultModel,处理后才可能是 redirectModel。
现在再返回去看 RequestMappingHlandlerAdapter 中的getModelAndView 方法 getModel 后判断 Model 是不是 RedirectAttributes 类型就清楚是怎么回事了。在 getModel 返回 redirectModel的情况下,在处理器中设置到 Model 中的参数就不会被使用了(设置 SessionAttribute 除外)。这样也没有什么危害,因为只有 redirect 的情况才会返回 redirectModel,而这种情况是不需要渲染页面的,所以 defaultModel 里的参数本来也没什么用。同样,如果返回 defaultModel,设置到 RedirectAttributes 中的参数也将丢弃,也就是说在返回的View 不是 redirect 类型时,即使处理器使用 RedirectAttributes 参数设置了值也不会传递到下一个请求。
另外,通过 @SessionAttribute 传递的参数是在 ModeIFactory 中的 updateModel 方法中设置的,那里使用了 mavContainer:.getDefaultModel 方法,这样就确保无论在什么情况下都是使用 defaultModel,也就是只有将参数设置到 Model 或者 ModeIMap 里才能使用 SessionAttribute缓存,设置到 RedirectAttributes 里的参数不可以。
ModelAndViewContainer 还提供了添加、合并和删除属性的方法,它们都是直接调用Model 操作的。
// ModelAndViewContainer public ModelAndViewContainer addAttribute(String name, @Nullable Object value) { getModel().addAttribute(name, value); return this; } public ModelAndViewContainer addAttribute(Object value) { getModel().addAttribute(value); return this; } public ModelAndViewContainer addAllAttributes(@Nullable Map<String, ?> attributes) { getModel().addAllAttributes(attributes); return this; } public ModelAndViewContainer removeAttributes(@Nullable Map<String, ?> attributes) { if (attributes != null) { for (String key : attributes.keySet()) { getModel().remove(key); } } return this; }
添加、删除属性都没什么需要说的,合并属性的逻辑是如果原来的Model 中不包含传入的属性则添加进去,如果原来 Model 中已经有了就不操作了,具体代码在 ModeIMap 中,如下:
// ModelMap public ModelMap mergeAttributes(@Nullable Map<String, ?> attributes) { if (attributes != null) { attributes.forEach((key, value) -> { if (!containsKey(key)) { put(key, value); } }); } return this; }
把这些弄明白 ModelAndViewContainer 基本就明白了,剩下的只是两个非常简单的功能。sessionStatus 属性就是在处理器中通知 SessionAttribute 已经使用完时所用到的 SessionStatus 类型的参数,它用于标示 SessionAttribute 是否已经使用完,如果使用完了则在 ModelFactory 的updateModel 方法中将 SessionAttribute 的相应参数清除,否则将当前 Model 的相应参数设置进去。
requestHandled 用于标示请求是否已经全部处理完,如果是就不再往下处理,直接返回。这里的全部处理完主要指已经返回 response,比如,在处理器返回值有 @ResponseBody 注释或者返回值为 HttpEntity 类型等情况都会将 requestHandled 设置为 true。
4 小结
最后我们这里画个图捋一下 RequestMappingHandlerAdapter 的处理过程:

也就是把我们上节的这个图细化一下:

下节我们就要重点看下 ServletInvocableHandlerMethod 的处理过程了哈,有理解不对的地方还请指正哈。

浙公网安备 33010602011771号