关联博文:
SpringMVC常见组件之HandlerAdapter分析
SpringMVC常见组件之HandlerMethodArgumentResolver解析
SpringMVC常见组件之HandlerMethodReturnValueHandler解析
这里框架背景为SpringBoot 2.2.4.RELEASE,不同版本下某些流程可能不同。
如果你曾经跟踪过请求流程或者在某个博客看到过请求流程源码,比如SpringBoot中添加@ResponseBody注解会发生什么?,你应该知道参数解析是由各种各样的HandlerMethodArgumentResolver(参数解析器)处理的。不同类型的参数会有对应的参数解析器处理。
【RequestMappingHandlerAdapter.afterPropertiesSet
① HandlerMethodArgumentResolver源码
public interface HandlerMethodArgumentResolver { //判断当前解析器是否能够解析该类型参数 boolean supportsParameter(MethodParameter parameter); //获取当前参数对象的值,通过mavContainer可以获取model,通过binderFactory可以获取WebDataBinder实例对象 //WebDataBinder用来进行数据绑定、参数类型转换 @Nullable Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception; }
在springboot启动时其中一步finishBeanFactoryInitialization会对一些bean进行预初始化,RequestMappingHandlerAdapter就是在这里被实例化的。
② 在afterPropertiesSet方法中,其会获取一些全局配置、参数解析器等
源码如下所示:
@Override public void afterPropertiesSet() { // 获取标注了@ControllerAdvice注解的bean initControllerAdviceCache(); //获取默认的参数解析器 if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } //获取@InitBinder注解使用的参数解析器 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); } }
这里有个细节需要注意,获取的默认参数解析器、@InitBinder注解使用的参数解析器或者返回结果处理器均是有序的列表。有序的意思是当遍历解析器找到当前参数合适的解析器时,如果前面的解析器能够处理,那么就会直接返回(即使后面有参数解析器同样能够处理)
如下所示,解析参数时首先遍历获取合适的参数解析器(HandlerMethodArgumentResolverComposite.getArgumentResolver):
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; }
RequestMappingHandlerAdapter实例化时注册了26个默认的参数解析器:
③ initControllerAdviceCache
源码如下:
privprivate void initControllerAdviceCache() { if (getApplicationContext() == null) { return; } //获取标注了注解@ControllerAdvice的类,封装为一个个ControllerAdviceBean并使用OrderComparator排序 List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); List<Object> requestResponseBodyAdviceBeans = new ArrayList<>(); //遍历adviceBeans for (ControllerAdviceBean adviceBean : adviceBeans) { Class<?> beanType = adviceBean.getBeanType(); if (beanType == null) { throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean); } //获取adviceBean 中@ModelAttribute注解且没有@RequestMapping注解的方法 Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS); if (!attrMethods.isEmpty()) { this.modelAttributeAdviceCache.put(adviceBean, attrMethods); } //获取adviceBean 中@InitBinder注解的方法 Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS); if (!binderMethods.isEmpty()) { this.initBinderAdviceCache.put(adviceBean, binderMethods); } //获取requestResponseBodyAdviceBeans类型的bean:RequestBodyAdvice或者ResponseBodyAdvice if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) { requestResponseBodyAdviceBeans.add(adviceBean); } } if (!requestResponseBodyAdviceBeans.isEmpty()) { this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans); } //几种类型数量统计 if (logger.isDebugEnabled()) { int modelSize = this.modelAttributeAdviceCache.size(); int binderSize = this.initBinderAdviceCache.size(); int reqCount = getBodyAdviceCount(RequestBodyAdvice.class); int resCount = getBodyAdviceCount(ResponseBodyAdvice.class); if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) { logger.debug("ControllerAdvice beans: none"); } else { logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize + " @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice"); } } }
④ 获取默认的参数解析器getDefaultArgumentResolvers
方法源码如下:
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(); // Annotation-based argument resolution //针对注解的参数解析器 resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); resolvers.add(new ServletModelAttributeMethodProcessor(false)); 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 (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all 这个注释很有意思Catch-all表示当你没有显示表明什么注解也不是httpsession map等基础参数类型时 //就会尝试使用下面这两种参数解析器 //1.RequestParamMethodArgumentResolver 针对简单类型 //2.针对复杂类型,如User resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); resolvers.add(new ServletModelAttributeMethodProcessor(true)); return resolvers; }
⑤ 获取InitBinder注解方法的参数解析器getDefaultInitBinderArgumentResolvers
方法源码如下(参考④ 这里不再解释):
private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(); // Annotation-based argument resolution resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new MatrixVariableMethodArgumentResolver()); resolvers.add(new MatrixVariableMapMethodArgumentResolver()); 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()); // Custom arguments if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } // Catch-all resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); return resolvers; }
⑥ 获取方法返回结果处理器getDefaultReturnValueHandlers
方法源码如下:
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() { List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(); // 处理单一返回类型 handlers.add(new ModelAndViewMethodReturnValueHandler()); handlers.add(new ModelMethodProcessor()); handlers.add(new ViewMethodReturnValueHandler()); handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(), this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager)); handlers.add(new StreamingResponseBodyReturnValueHandler()); handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); handlers.add(new HttpHeadersReturnValueHandler()); handlers.add(new CallableMethodReturnValueHandler()); handlers.add(new DeferredResultMethodReturnValueHandler()); handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory)); // 处理方法上面有@ModelAttribute 或者@ResponseBody注解 handlers.add(new ModelAttributeMethodProcessor(false)); handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)); // 处理多个返回类型 handlers.add(new ViewNameMethodReturnValueHandler()); handlers.add(new MapMethodProcessor()); // 自定义返回结果处理器 if (getCustomReturnValueHandlers() != null) { handlers.addAll(getCustomReturnValueHandlers()); } // Catch-all---最后的希望 if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) { handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers())); } else { handlers.add(new ModelAttributeMethodProcessor(true)); } return handlers; }
2】不同参数解析器处理对应的参数类型
① 简单类型默认参数解析器RequestParamMethodArgumentResolver
RequestParamMethodArgumentResolver主要用来处理标注了@RequestParam注解的方法参数,有时会与其他解析器配合使用。如与MultipartResolver配合对MultipartFile类型参数进行处理。
当方法参数没有使用注解标明的时候,通常RequestParamMethodArgumentResolver会默认用来处理简单类型的参数如int long String等。
其类继承示意图如下(绿色虚线表示其实现接口):
② RequestParamMapMethodArgumentResolver解析Map类型的参数
RequestParamMapMethodArgumentResolver用来处理参数类型为Map(或MultiValueMap)、参数使用了@RequestParam注解但是并没有指定@RequestParam注解的name属性值。
解析后的map会包含请求中所有与的参数键值对,或者是值类型为MultipartFile的参数键值对
③ URI参数解析PathVariableMethodArgumentResolver
PathVariableMethodArgumentResolver将会从URI上面解析参数赋值给方法参数,也就是方法参数标注了@PathVariable注解。
如果参数类型为Map时,则必须指定@PathVariable注解的name属性或者value属性(但是前提示已经注册了一个合适的类型转换器或者PropertyEditor,否则将会抛出异常!)。
如果@PathVariable注解指定了name属性或者value属性,但是与URI占位符里面变量不一致,将会抛出类似于org.springframework.web.bind.MissingPathVariableException: Missing URI template variable 'param' for method parameter of type Map异常。
④ URI参数解析之PathVariableMapMethodArgumentResolver
PathVariableMapMethodArgumentResolver将会解析标注了@PathVariable注解、参数类型为Map且没有指定@PathVariable注解的name属性或value属性。以占位符里面的id为key,参数值为value赋值给param。
⑤ MatrixVariableMethodArgumentResolver
解析@MatrixVariable注解标注的参数,如果参数类型是Map,通常会被MatrixVariableMapMethodArgumentResolver解析器处理除非该注解指定了name属性。
⑥ MatrixVariableMapMethodArgumentResolver
解析使用了@MatrixVariable标注的Map类型的参数,且@MatrixVariable注解没有指定name属性。
⑦ 复杂类型对象解析器ServletModelAttributeMethodProcessor与ModelAttributeMethodProcessor
ServletModelAttributeMethodProcessor是ModelAttributeMethodProcessor的子类,可以解析复杂类型对象,如User。当构造方法参数annotationNotRequired为true时,那么无论方法参数是否使用了@ModelAttribute注解,都会被这两个解析器处理。该参数解析器同样适用于@ModelAttribute注解的方法。
数据绑定过程大概如下
- 确定key
如果使用了@ModelAttribute注解且指定了其name属性,那么参数的key就是name属性值;否则为对象首字母小写,如TbSysAdmin–>tbSysAdmin。
确定value/target对象
尝试从model中获取,如果获取到就返回;
尝试从UriTemplateVariables或request根据key获取value,如果获取到就返回;
使用构造器实例化一个空对象-target
bindRequestParameters(binder, webRequest)
从请求中获取参数键值对对binder中的target进行数据绑定
把得到的参数值扔给方法参数
⑧ RequestResponseBodyMethodProcessor
该注解通常用来处理方法参数上标注了@RequestBody注解或者方法上面标注了@ResponseBody注解。其会使用HttpMessageConverter从request中读取数据赋值给参数或者把方法返回结果写到response中。其同样作为返回结果处理器使用。
⑨ RequestPartMethodArgumentResolver
该解析器用来处理如下类型参数:
- 标注了@RequestPart注解的参数
- 与MultipartResolver配合使用解析MultipartFile类型
- 与Servlet 3.0 multipart requests配合处理javax.servlet.http.Part类型
(10)请求头处理RequestHeaderMethodArgumentResolver
解析标注了@RequestHeader注解的方法参数,除了Map类型。
(11)请求头处理RequestHeaderMapMethodArgumentResolver
解析标注了@RequestHeader注解的Map类型的参数。
(12)Cookie解析器ServletCookieValueMethodArgumentResolver
从HttpServletRequest获取cookie 值。
(13)${...}
或#{...}
表达式解析器ExpressionValueMethodArgumentResolver
解析标注了 @Value注解的方法参数,该注解通常使用${...}或者#{...}
来表示值。
(14)session属性解析器SessionAttributeMethodArgumentResolver
该解析器用来处理标注了@SessionAttribute的方法参数,也就是根据name从session里面获取值。
(15)request属性解析器RequestAttributeMethodArgumentResolver
该解析器用来处理标注了@RequestAttribute的方法参数,也就是根据name从request里面获取值。
下面是Type-based argument resolution的一些参数解析器。
(16)请求相关的参数解析器ServletRequestMethodArgumentResolver
该解析器用来处理如下类型的参数:
- WebRequest
- ServletRequest
- MultipartRequest
- HttpSession
- PushBuilder
- Principal
- InputStream
- Reader
- HttpMethod
- Locale
- TimeZone
- java.time.ZoneId
(17)响应相关的参数解析器ServletResponseMethodArgumentResolver
该参数解析器主要用来处理如下类型参数:
- ServletResponse
- OutputStream
- Writer
(18)HttpEntityMethodProcessor
该解析器用来处理HttpEntity和RequestEntity类型参数,以及HttpEntity和ResponseEntity返回类型。其同样作为返回结果处理器使用。
(19)重定向属性解析RedirectAttributesMethodArgumentResolver
该解析器用来处理方法参数为RedirectAttributes类型的,其必须在解析Model类型参数的ModelMethodProcessor和解析Map类型参数的MapMethodProcessor之前加载。因为后者均可以尝试解析RedirectAttributes类型的参数。
那么什么是RedirectAttributes?如何使用RedirectAttributes?
RedirectAttributes是继承自Model的一个特殊接口,可以在请求重定向时传递参数使用。唯一实现类为RedirectAttributesModelMap其有属性ModelMap flashAttributes用来存放重定向过程中的数据给重定向后的方法使用:
在Controller中使用RedirectAttributes 实例如下:
@RequestMapping(value = "/accounts", method = RequestMethod.POST) public String handle(Account account, BindingResult result, RedirectAttributes redirectAttrs) { if (result.hasErrors()) { return "accounts/new"; } // Save account ... redirectAttrs.addAttribute("id", account.getId()).addFlashAttribute("message", "Account created!"); return "redirect:/accounts/{id}"; }
(20)Model类型参数解析器ModelMethodProcessor
该解析器用来处理方法参数类型为Model或者返回结果为Model,其应该注册于那些支持@ModelAttribute、@ResponseBody的处理器之前。其同样作为返回结果处理器使用。
那么什么是Model?
其实java5定义的一个容器接口,用来保存属性键值。通常获取model中的数据是将其作为一个Map进行操作。实现类如:
其中最常见的BindingAwareModelMap类继承图如下:
其中最常见的BindingAwareModelMap类继承图如下:
(22)Errors类型参数解析器ErrorsMethodArgumentResolver
解析Errors类型的方法参数。Errors通常与model attribute一起出现,作为接收错误对象的参数使用。
(23)SessionStatus类型参数解析器SessionStatusMethodArgumentResolver
解析SessionStatus类型的参数,从ModelAndViewContainer获取SessionStatus:
@Override public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null, "ModelAndViewContainer is required for session status exposure"); return mavContainer.getSessionStatus(); }
(24)UriComponentsBuilderMethodArgumentResolver
解析UriComponentsBuilder类型的参数。
(25)默认的、简单类型参数解析器RequestParamMethodArgumentResolver
这里useDefaultResolution 为true:
new RequestParamMethodArgumentResolver(getBeanFactory(), true) public RequestParamMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory, boolean useDefaultResolution) { super(beanFactory); this.useDefaultResolution = useDefaultResolution; }
(26)默认的、复杂类型参数解析器ServletModelAttributeMethodProcessor
这里annotationNotRequired为true,其同样作为返回结果处理器使用。
new ServletModelAttributeMethodProcessor(true) public ServletModelAttributeMethodProcessor(boolean annotationNotRequired) { super(annotationNotRequired); }
【3】返回结果处理器与返回结果类型
① 视图处理器ModelAndViewMethodReturnValueHandler
处理返回结果类型为ModelAndView的方法,将会把view和model赋值给mavContainer。如果返回结果为null,则ModelAndViewContainer.setRequestHandled(true)来标明请求已经被直接处理完毕。
② Model返回结果处理器ModelMethodProcessor
处理方法参数为Model类型和返回结果类型为Model的方法。其不仅仅是返回结果处理器,也是方法参数解析器。
③ View返回结果处理器ViewMethodReturnValueHandler
该解析器用来处理返回结果为View类型的方法。
④ ResponseBodyEmitter返回结果处理ResponseBodyEmitterReturnValueHandler
该解析器用来处理返回结果为ResponseBodyEmitter或其子类如用ResponseEntity包装的。
⑤ StreamingResponseBodyReturnValueHandler
该解析器用来处理返回结果为StreamingResponseBody和ResponseEntity<StreamingResponseBody>
的方法。
⑥ HttpEntityMethodProcessor
作为参数解析器用来解析HttpEntity和RequestEntity。作为返回结果处理器用来处理HttpEntity和ResponseEntity的类型。
⑦ HttpHeadersReturnValueHandler
处理返回结果类型为HttpHeaders的方法。
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { //设置请求处理标志位true mavContainer.setRequestHandled(true); Assert.state(returnValue instanceof HttpHeaders, "HttpHeaders expected"); HttpHeaders headers = (HttpHeaders) returnValue; //如果headers 不为空,就放入outputMessage.getHeaders()中 if (!headers.isEmpty()) { HttpServletResponse servletResponse = webRequest.getNativeResponse(HttpServletResponse.class); Assert.state(servletResponse != null, "No HttpServletResponse"); ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(servletResponse); outputMessage.getHeaders().putAll(headers); outputMessage.getBody(); // flush headers } }
⑧ CallableMethodReturnValueHandler
处理返回结果类型为Callable的方法,通常用于异常处理。
public class CallableMethodReturnValueHandler implements HandlerMethodReturnValueHandler { @Override public boolean supportsReturnType(MethodParameter returnType) { return Callable.class.isAssignableFrom(returnType.getParameterType()); } @Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue == null) { mavContainer.setRequestHandled(true); return; } //如下进行异步回调 Callable<?> callable = (Callable<?>) returnValue; WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(callable, mavContainer); } }
DeferredResultMethodReturnValueHandler
处理方法返回结果类型为DeferredResult、ListenableFuture以及CompletionStage的方法。
⑩异步任务处理器AsyncTaskMethodReturnValueHandler
处理方法返回结果为WebAsyncTask的。
(11)@ModelAttribute标注的方法处理器ModelAttributeMethodProcessor
new ModelAttributeMethodProcessor(false)
作为参数解析器用来处理@ModelAttribute标注的方法参数,作为结果处理器用来处理@ModelAttribute标注的方法返回结果。
作为参数解析器用来处理@ModelAttribute标注的方法参数,作为结果处理器用来处理@ModelAttribute标注的方法返回结果。
(12)RequestResponseBodyMethodProcessor
new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice)
作为参数解析器用来处理@RequestBody注解的参数,作为返回结果处理器用来处理方法标注了@ResponseBody的返回结果。通常会与HttpMessageConverter配合使用,用来从request读取数据或向response写入数据。
(13)视图名字结果处理器ViewNameMethodReturnValueHandler
可以处理如下返回结果类型:
- void
- String
- CharSequence
- StringBuilder
- GString
将返回结果作为view name进行处理。
(14)MapMethodProcessor
作为参数解析器用来处理Map类型参数,作为返回结果处理器用来处理Map类型结果。当作为前者时,其会从mavContainer获取model赋值给方法参数;当作为后者时,其会把返回结果放入mavContainer的model中。
(15)最后的希望ModelAndViewResolverMethodReturnValueHandler与ModelAttributeMethodProcessor
如果有ModelAndViewResolver那么会注册ModelAndViewResolverMethodReturnValueHandler否则会注册new ModelAttributeMethodProcessor(true),true的意思是不管是否有@ModelAttribute注解,复杂对象类型的返回结果最后会被该解析器尝试处理(非空的返回结果会被放入mavContainer的model中)。
if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) { handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers())); } else { handlers.add(new ModelAttributeMethodProcessor(true)); }
ModelAttributeMethodProcessor已经很熟悉了,那么ModelAndViewResolverMethodReturnValueHandler是什么?