首页 > 技术文章 > Spring Web MVC工作原理

z-sm 2020-05-06 10:03 原文

总结

在javax.servlet.http.HttpServlet的基础上添加额外功能,包括参数解析、请求拦截器、异常处理、视图渲染等。

 

Handler

springframework中对http request的最细粒度的处理者,类似于javax中的Servlet。类型有:

org.springframework.web.HttpRequestHandler:接口,有很多实现类,如ResourceHttpRequestHandler专门用于处理静态资源请求

org.springframework.web.servlet.mvc.Controller:接口,有很多实现类,如ServletWrappingController。

org.springframework.web.method.HandlerMethod:类,springframework的@Controller中带有@RequestMapping的方法默认为此类的handler

 

HandlerExecutionChain

对Handler的进一步封装,将一个handler及与该handler相关的各interceptor封装在一起。

 

HandlerAdapter

具体执行handler方法者,DispatcherServlet会根据handler的类型交由相应的handler adapter执行。

默认的HandlerAdapter类型

默认会添加三个HandlerAdapter到application context:

class org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,处理interface org.springframework.web.HttpRequestHandler类型的handler

class org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,处理interface org.springframework.web.servlet.mvc.Controller类型的handler

class org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,处理class org.springframework.web.method.HandlerMethod类型的handler,@Controller中的handler通过此adapter处理

@ControllerAdvice + RequestBodyAdvice/ResponseBodyAdvice/@ModelAttribute/@InitBinder 的全局处理功能也是由RequestMappingHandlerAdapter来实现的。详情可参阅:https://blog.csdn.net/andy_zhang2007/article/details/100041219

handler方法的执行过程

(参阅:https://blog.csdn.net/andy_zhang2007/article/details/99689573

Spring MVC中,一般来讲,控制器指的是开发人员使用了注解@Controller这样的类,而控制器方法,是控制器类中使用了注解@RequestMapping的那些方法。控制器方法能处理哪些请求,就是通过这些@RequestMapping注解信息来定义的,DispatcherServlet会把这些信息加载保存到一个RequestMappingHandlerMapping对象中,当相应的请求到达时,DispatcherServlet会通过该信息找到相应的控制器方法,然后通过调用RequestMappingHandlerAdapter执行控制器方法,完成对请求的处理。

handler方法的执行逻辑主要在RequestMappingHandlerAdapter中。

1 RequestMappingHandlerAdapter整体处理流程的相关源码:

 1 // RequestMappingHandlerAdapter 代码片段
 2     // 返回值是一个 ModelAndView
 3     // 如果需要调用者后续解析和渲染视图,则该返回值 ModelAndView 不会为 null
 4     // 如果请求已经被该方法完全处理,不需要调用者后续解析和渲染视图,则返回值 ModelAndView 会是 null
 5     @Nullable
 6     protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
 7             HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
 8 
 9        // 将  HttpServletRequest/HttpServletResponse 对象包装成一个 ServletWebRequest 对象
10         ServletWebRequest webRequest = new ServletWebRequest(request, response);
11         try {
12           // 准备工作,设置调用控制器方法所需要的各种数据,或者工具组件
13             WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
14             ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
15 
16            // 将目标控制器方法 handlerMethod 包装成一个 ServletInvocableHandlerMethod,
17            // 这里主要是针对 Servlet 环境的一些特殊处理和包装,参数传递
18             ServletInvocableHandlerMethod invocableMethod =
19                 createInvocableHandlerMethod(handlerMethod);
20             if (this.argumentResolvers != null) {
21                 invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
22             }
23             if (this.returnValueHandlers != null) {
24                 invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
25             }
26             invocableMethod.setDataBinderFactory(binderFactory);
27             invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
28 
29           // ModelAndViewContainer 是一个调用目标控制器方法过程中,记录参数解析,返回值处理的一些
30           // 决策信息的一个容器类型/持有器类型,这里准备出这样一个对象,供控制器方法调用过程中使用
31             ModelAndViewContainer mavContainer = new ModelAndViewContainer();
32           // 将请求中可能存在的 FlashMap 属性添加到 mavContainer
33             mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
34           // 1. 获取所有 @SessionAttributes 属性到   mavContainer
35           // 2. 执行所有 @ModelAttribute 方法
36           // 3. 找到所有使用了注解 @ModelAttribute 并且也属于 @SessionAttributes 列表的
37           //    参数将其添加到 mavContainer
38             modelFactory.initModel(webRequest, mavContainer, invocableMethod);
39           // 设置重定向时如果 RedirectAttributes 属性未设置,是否要使用缺省 Model,
40           // 缺省值为 false, 表示即使重定向时 RedirectAttributes 属性未设置,也不要使用缺省 Model
41             mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
42 
43           // 对异步Web请求的处理的准备工作,本系列文章中只介绍同步请求处理,所以这里忽略对这一部分的介绍
44             AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
45             asyncWebRequest.setTimeout(this.asyncRequestTimeout);
46 
47             WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
48             asyncManager.setTaskExecutor(this.taskExecutor);
49             asyncManager.setAsyncWebRequest(asyncWebRequest);
50             asyncManager.registerCallableInterceptors(this.callableInterceptors);
51             asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
52 
53             if (asyncManager.hasConcurrentResult()) {
54                 Object result = asyncManager.getConcurrentResult();
55                 mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
56                 asyncManager.clearConcurrentResult();
57                 LogFormatUtils.traceDebug(logger, traceOn -> {
58                     String formatted = LogFormatUtils.formatValue(result, !traceOn);
59                     return "Resume with async result [" + formatted + "]";
60                 });
61                 invocableMethod = invocableMethod.wrapConcurrentResult(result);
62             }
63 
64 
65           // 万事俱备,现在执行目标控制器方法,从此入口进去,会看到具体执行过程的更多步骤逻辑,
66           // 这里也是我们将要展开介绍的地方
67             invocableMethod.invokeAndHandle(webRequest, mavContainer);
68             if (asyncManager.isConcurrentHandlingStarted()) {
69                 return null;
70             }
71 
72           // 目标控制器方法执行完结,请求处理已经完成,执行过程中的各种决定信息都记录在了
73           // mavContainer 中,现在结合相关组件构造一个 ModelAndView 对象,后续调用者
74           // 有可能还有基于这些信息解析和渲染视图
75           // 如果控制器方法已经完全处理了该请求,无需后续处理,则 mavContainer 会包含
76           // 这样的信息 : #isRequestHandled == true, 从而这里的 ModelAndView 返回值
77           // 会是 null
78             return getModelAndView(mavContainer, modelFactory, webRequest);
79         }
80         finally {
81             webRequest.requestCompleted();
82         }
83     }
RequestMappingHandlerAdapter#invodeHandlerMethod

2 RequestMappingHandlerAdapter中会初始化参数解析、参数绑定、返回值解析相关的默认解析器。用户也可以在此添加自己的解析器。分别把它们封装成一个Composite,相关源码:

 1     // RequestMappingHandlerAdapter 代码片段
 2     // RequestMappingHandlerAdapter 的初始化方法
 3     @Override
 4     public void afterPropertiesSet() {
 5         // Do this first, it may add ResponseBody advice beans
 6         initControllerAdviceCache();
 7 
 8         if (this.argumentResolvers == null) {
 9             // 获取缺省的 HandlerMethodArgumentResolver, 多个
10             List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
11             // 构造 HandlerMethodArgumentResolverComposite 对象,
12             // 包装缺省的多个 HandlerMethodArgumentResolver
13             this.argumentResolvers = 
14                 new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
15         }
16         if (this.initBinderArgumentResolvers == null) {
17             List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
18             this.initBinderArgumentResolvers = 
19                 new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
20         }
21         if (this.returnValueHandlers == null) {
22             List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
23             this.returnValueHandlers = 
24                 new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
25         }
26     }
#afterPropertiesSet

3 主要处理过程包括:

创建ModelAndViewContainer对象

该对象用于记录执行handler方法需要的各种决定信息,包括参数、返回值解析器等,这些执行决定信息最终用于生成调用者用于解析和渲染视图所需的ModelAndView对象。

请求实参值的获取

这里的参数可能是url参数、header参数、cookie参数、session参数、body参数等,既可以是简单数据也可以是json/矩阵等结构化数据。主要逻辑在:RequestMappingHandlerAdapter#invokeHandlerMethod==> ServletInvocableHandlerMethod#invokeAndHandle ==> InvocableHandlerMethod#invokeForRequest ==> getMethodArgumentValues,相关源码:

 1     protected Object[] getMethodArgumentValues(NativeWebRequest request, 
 2                                     @Nullable ModelAndViewContainer mavContainer,
 3                                     Object... providedArgs) throws Exception {
 4 
 5        // getMethodParameters 返回的是控制器方法的参数列表,
 6        // 如果该参数列表为空,直接返回 EMPTY_ARGS :  new Object[0]       
 7         if (ObjectUtils.isEmpty(getMethodParameters())) {
 8             return EMPTY_ARGS;
 9         }
10         
11        // 控制器方法参数列表不为空的情况 
12         MethodParameter[] parameters = getMethodParameters();
13         
14        // 构建对象数组 args,让其元素个数跟控制器方法参数个数相同,因为这个对象数组
15        // 是用来保存相应参数的参数值的
16        // 这个对象数组 args 会被下面的代码逻辑填充,然后被作为实参调用目标控制器方法
17         Object[] args = new Object[parameters.length];
18         
19        // 从左至右逐个遍历每个控制器方法参数,从请求上下文中获取相应的参数值放到args相应的位置 
20         for (int i = 0; i < parameters.length; i++) {
21           // 一次 for 循环完成一个控制器方法参数的参数值解析逻辑
22           
23           
24           // 获取控制器方法第 i 个参数
25             MethodParameter parameter = parameters[i];
26           //  设置方法参数名称发现器,缺省会是 DefaultParameterNameDiscoverer,
27           // 方法参数名称发现器用于发现该参数的名称,该名称用于从请求上下文中分析该参数的参数值
28             parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
29           //  首先尝试  从 providedArgs 中获取该参数的参数值,
30           // Spring MVC 默认情况下 providedArgs 总是为空,所以这里关于应用 providedArgs 的
31           // 逻辑默认总是空转,不产生实际效果
32             args[i] = findProvidedArgument(parameter, providedArgs);
33             if (args[i] != null) {
34               // 如果通过 providedArgs 应用参数值成功则表示该参数参数值解析成功,跳过
35               // 随后逻辑继续下一个参数的参数值解析逻辑
36                 continue;
37             }
38            
39            // this.resolvers 是调用者 RequestMappingHandlerAdapter 设置给当前对象的
40            // 多个参数解析器的组合对象 HandlerMethodArgumentResolverComposite,
41            
42            // 检测当前参数是否被 this.resolvers 支持,如果不支持则抛出异常
43             if (!this.resolvers.supportsParameter(parameter)) {
44                 throw new IllegalStateException(formatArgumentError(parameter, 
45                                                         "No suitable resolver"));
46             }
47             
48            // 当前参数经过检测被 this.resolvers 支持,现在尝试使用 this.resolvers 从请求上下文中分析其值,
49            // 注意,this.resolvers 检测某个参数是否被支持的逻辑通常仅仅考虑参数的某些特征 : 比如数据类型
50            // 等等,所以一个参数被支持,并不代表它的值能被正确解析,因为数据的内容或者格式有可能在解析时会
51            // 遇到问题。
52            // 下面的 try-catch 就是处理这种参数值解析过程中遇到的异常的。
53             try {
54                 args[i] = this.resolvers.resolveArgument(parameter, mavContainer, 
55                             request, this.dataBinderFactory);
56             }
57             catch (Exception ex) {
58                 // Leave stack trace for later, exception may actually be resolved and handled..
59                 if (logger.isDebugEnabled()) {
60                     String error = ex.getMessage();
61                     if (error != null 
62                         && !error.contains(parameter.getExecutable().toGenericString())) {
63                         logger.debug(formatArgumentError(parameter, error));
64                     }
65                 }
66                 throw ex;
67             }
68         }
69         
70        // 现在针对每个控制器方法的参数值已经被解析到对象数组中 args 了, 返回该对象数组,
71        // 下一步就是使用这些实参对象调用目标控制器方法了。
72         return args;
73     }
View Code

handler方法实参值的解析和绑定 HandlerMethodArgumentResolver

实际直接用的是HandlerMethodArgumentResolverComposite(这点原理上与HandleExceptionResolverComposite类似):在RequestMappingHandlerAdapter初始化时,会构建一个HandlerMethodArgumentResolverComposite对象,该对象是多个HandlerMethodArgumentResolver的一个集合。使用时遍历这些集合以寻找第一个能处理参数的resolver来处理参数。相关逻辑源码见上面的初始化代码。

SpringMVC框架提供了很多MethodArgumentResolver的实现类来解析和绑定各种类型的请求参数:

通常我们可以在handler方法中直接声明HttpServletRequest、HttpSession、InputStream等参数,框架会在收到请求时自动解析参数传给我们的handler方法,其背后就是 ServletRequestMethodArgumentResolver 起的作用。Handler方法中支持的参数如上表所示,关于这些参数的具体含义或使用,可参阅:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web.html#mvc-ann-arguments

默认会添加若干HandlerMethodArgumentResolver,相关源码:

 1     /**
 2      * Return the list of argument resolvers to use including built-in resolvers
 3      * and custom resolvers provided via {@link #setCustomArgumentResolvers}.
 4      */
 5     private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
 6         List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
 7 
 8         // Annotation-based argument resolution
 9         resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
10         resolvers.add(new RequestParamMapMethodArgumentResolver());
11         resolvers.add(new PathVariableMethodArgumentResolver());
12         resolvers.add(new PathVariableMapMethodArgumentResolver());
13         resolvers.add(new MatrixVariableMethodArgumentResolver());
14         resolvers.add(new MatrixVariableMapMethodArgumentResolver());
15         resolvers.add(new ServletModelAttributeMethodProcessor(false));
16         resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
17         resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
18         resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
19         resolvers.add(new RequestHeaderMapMethodArgumentResolver());
20         resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
21         resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
22         resolvers.add(new SessionAttributeMethodArgumentResolver());
23         resolvers.add(new RequestAttributeMethodArgumentResolver());
24 
25         // Type-based argument resolution
26         resolvers.add(new ServletRequestMethodArgumentResolver());
27         resolvers.add(new ServletResponseMethodArgumentResolver());
28         resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
29         resolvers.add(new RedirectAttributesMethodArgumentResolver());
30         resolvers.add(new ModelMethodProcessor());
31         resolvers.add(new MapMethodProcessor());
32         resolvers.add(new ErrorsMethodArgumentResolver());
33         resolvers.add(new SessionStatusMethodArgumentResolver());
34         resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
35 
36         // Custom arguments
37         if (getCustomArgumentResolvers() != null) {
38             resolvers.addAll(getCustomArgumentResolvers());
39         }
40 
41         // Catch-all
42         resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
43         resolvers.add(new ServletModelAttributeMethodProcessor(true));
44 
45         return resolvers;
46     }
47 
48 
49 
50 
51 
52     /**
53      * Return the list of argument resolvers to use for {@code @InitBinder}
54      * methods including built-in and custom resolvers.
55      */
56     private List<HandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {
57         List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
58 
59         // Annotation-based argument resolution
60         resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
61         resolvers.add(new RequestParamMapMethodArgumentResolver());
62         resolvers.add(new PathVariableMethodArgumentResolver());
63         resolvers.add(new PathVariableMapMethodArgumentResolver());
64         resolvers.add(new MatrixVariableMethodArgumentResolver());
65         resolvers.add(new MatrixVariableMapMethodArgumentResolver());
66         resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
67         resolvers.add(new SessionAttributeMethodArgumentResolver());
68         resolvers.add(new RequestAttributeMethodArgumentResolver());
69 
70         // Type-based argument resolution
71         resolvers.add(new ServletRequestMethodArgumentResolver());
72         resolvers.add(new ServletResponseMethodArgumentResolver());
73 
74         // Custom arguments
75         if (getCustomArgumentResolvers() != null) {
76             resolvers.addAll(getCustomArgumentResolvers());
77         }
78 
79         // Catch-all
80         resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
81 
82         return resolvers;
83     }
#getDefaultArgumentResolvers #getDefaultInitBinderArgumentResolvers 

在为每个形参查找参数解析器时会按照添加顺序寻找第一个support解析该参数的resolver。

实参值解析和绑定的实现——以AbstractNamedValueMethodArgumentResolver为例,很多上述Resolver是此的子类。相关源码:

  1 package org.springframework.web.method.annotation;
  2 
  3 // 省略 import 行
  4 
  5 
  6 public abstract class AbstractNamedValueMethodArgumentResolver 
  7                         implements HandlerMethodArgumentResolver {
  8 
  9     // 记录当前 Spring IoC 容器
 10     @Nullable
 11     private final ConfigurableBeanFactory configurableBeanFactory;
 12 
 13     // 使用bean定义进行表达式求值的上下文对象
 14     @Nullable
 15     private final BeanExpressionContext expressionContext;
 16 
 17     // 用于缓存找到的命名值信息
 18     private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache 
 19             = new ConcurrentHashMap<>(256);
 20 
 21 
 22     public AbstractNamedValueMethodArgumentResolver() {
 23         this.configurableBeanFactory = null;
 24         this.expressionContext = null;
 25     }
 26 
 27     /**
 28      * Create a new {@link AbstractNamedValueMethodArgumentResolver} instance.
 29      * @param beanFactory a bean factory to use for resolving ${...} placeholder
 30      * and #{...} SpEL expressions in default values, or {@code null} if default
 31      * values are not expected to contain expressions
 32      */
 33     public AbstractNamedValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
 34         this.configurableBeanFactory = beanFactory;
 35        // 初始化使用bean定义进行表达式求值的上下文对象
 36        // 仅在  this.configurableBeanFactory 被设置为非 null 时真正做初始化
 37         this.expressionContext =
 38                 (beanFactory != null 
 39                             ? new BeanExpressionContext(beanFactory, new RequestScope()) 
 40                             : null);
 41     }
 42 
 43 
 44     // 接口 HandlerMethodArgumentResolver 所定义的方法,解析参数
 45     // 1. parameter 是要解析参数值的控制器方法参数
 46     // 2. mavContainer 是一个容器对象,用来保存控制器方法执行过程中各种决定信息,本方法并未使用它
 47     // 3. webRequest 包装了当前请求对象和响应对象,并且能返回 Servlet 规范定义的请求和响应对象
 48     // 4. binderFactory 是 数据绑定器工厂,用于生成数据绑定器,
 49     @Override
 50     @Nullable
 51     public final Object resolveArgument(MethodParameter parameter, 
 52                     @Nullable ModelAndViewContainer mavContainer,
 53                     NativeWebRequest webRequest, 
 54                     @Nullable WebDataBinderFactory binderFactory) throws Exception {
 55 
 56        // 获取指定控制器方法参数  parameter 命名值描述信息 namedValueInfo
 57         NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
 58         MethodParameter nestedParameter = parameter.nestedIfOptional();
 59 
 60        //  namedValueInfo.name 是参数的名称字符串,不过该字符串可能是个表达式,需要进一步解析为
 61        // 最终的参数名称,下面的 resolveStringValue 语句就是对该名字进行表达式求值,从而得到解析后的
 62        // 控制器方法参数名称,此名称是从请求上下文中获取相应参数值的关键信息 
 63         Object resolvedName = resolveStringValue(namedValueInfo.name);
 64         if (resolvedName == null) {
 65            // 异常处理
 66             throw new IllegalArgumentException(
 67                     "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
 68         }
 69 
 70        // 根据控制器方法参数名称从请求上下文中尝试分析得到该参数的参数值 
 71         Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
 72         if (arg == null) {
 73           // 没能从请求上下文中分析得到参数值的分支 ,也就是 arg 为 null 的情况
 74             if (namedValueInfo.defaultValue != null) {
 75                // 对该参数指定了缺省值的情况 
 76               // 尝试应用缺省值,缺省值通过对 namedValueInfo.defaultValue 进行表达式求值得到
 77                 arg = resolveStringValue(namedValueInfo.defaultValue);
 78             }
 79             else if (namedValueInfo.required && !nestedParameter.isOptional()) {             
 80              // 没能从请求上下文中分析得到参数值,并且没有指定缺省值, 并且被指定为必要参数的情况的处理
 81              // 也可以概括为必要参数的值缺失情况的处理
 82              // 参考注解 @RequestParam 的属性 required
 83              // AbstractNamedValueMethodArgumentResolver  这里的缺省实现是抛出异常 :
 84              // ServletRequestBindingException
 85                 handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
 86             }
 87           
 88           // 对 null 参数值的处理 :
 89           // 1. 如果参数类型为 Boolean 或者 boolean, 则 null 被理解成 Boolean.FALSE ;
 90           // 2. 如果参数类型为其他 Java 基本数据类型,则抛出异常 IllegalStateException,
 91           //  因为一个除了 boolean 之外的其他基本数据类型,是无法接受一个 null 值的 ;
 92             arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
 93         }
 94         else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
 95           // arg 为0长度字符串并且有缺省值可以应用的情况的处理  
 96             arg = resolveStringValue(namedValueInfo.defaultValue);
 97         }
 98 
 99        // 现在, arg 变量值是经过各种解析将要应用到相应控制器方法参数的参数值,
100        // 但是,从请求上下文中分析得到的 arg 通常是字符串类型,该类型跟目标控制器方法参数的类型
101        // 通常都是不同的,所以现在需要尝试从值 arg 转换得到目标控制器方法参数类型的对象,
102        // 例子 :
103        // 1. arg 为字符串 "1" , 目标类型为 int, 则转换后的 arg 会是整数 1
104        // 2. arg 为字符串 "test", 目标类型为 int, 则转换失败,抛出异常 TypeMismatchException       
105 
106         if (binderFactory != null) {
107           // 使用 binderFactory 数据绑定工厂创建数据绑定器,用于数据类型的转换
108             WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
109           // 尝试转换并捕捉相应的异常  
110             try {
111                 arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
112             }
113             catch (ConversionNotSupportedException ex) {
114               // 源类型 和 目标类型之间没有合适的转换器
115                 throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
116                         namedValueInfo.name, parameter, ex.getCause());
117             }
118             catch (TypeMismatchException ex) {
119               // 源类型 和 目标类型之间有合适的转换器,但是转换动作执行失败,通常是源类型数据格式出了问题,
120               // 或者数据提供方和数据接收方在该种格式数据接收格式的沟通上出了问题
121                 throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
122                         namedValueInfo.name, parameter, ex.getCause());
123 
124             }
125         }
126 
127        // 上面是从请求中分析得到的参数值向目标控制器方法参数进行类型转换的逻辑,如果木有出现异常,
128        // 程序执行会走到这里,此时 arg 对象是类型为目标控制器方法参数类型的对象,该值可以应用到
129        // 目标方法参数了。
130       // 在将 arg 返回给调用者真正应用到目标方法参数之前,当前对象还可以通过 handleResolvedValue
131       // 方法对 arg 做一番调整。不过在抽象类 AbstractNamedValueMethodArgumentResolver 中,
132       // 此方法是一个空方法,也就是不做任何调整,但是具体实现子类可以覆盖实现该方法实现自己的逻辑。
133         handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
134 
135        // 从请求上下文中分析参数值,应用缺省值,处理 null 值,源类型到目标类型的类型转换都已经做完了,
136        // 大功告成,将此分析得到的参数值 arg 返回给调用者让其继续接下来的后续逻辑
137         return arg;
138     }
139 
140     /**
141      * Obtain the named value for the given method parameter.
142      */
143     private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
144         NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
145         if (namedValueInfo == null) {
146             namedValueInfo = createNamedValueInfo(parameter);
147             namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
148             this.namedValueInfoCache.put(parameter, namedValueInfo);
149         }
150         return namedValueInfo;
151     }
152 
153     /**
154      * Create the {@link NamedValueInfo} object for the given method parameter. 
155      * Implementations typically retrieve the method annotation by means of 
156      * {@link MethodParameter#getParameterAnnotation(Class)}.
157      * @param parameter the method parameter
158      * @return the named value information
159      */
160     protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter);
161 
162     /**
163      * Create a new NamedValueInfo based on the given NamedValueInfo with sanitized values.
164      */
165     private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
166         String name = info.name;
167         if (info.name.isEmpty()) {
168             name = parameter.getParameterName();
169             if (name == null) {
170                 throw new IllegalArgumentException(
171             "Name for argument type [" + parameter.getNestedParameterType().getName() +
172             "] not available, and parameter name information not found in class file either.");
173             }
174         }
175         String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) 
176                     ? null 
177                     : info.defaultValue);
178         return new NamedValueInfo(name, info.required, defaultValue);
179     }
180 
181     /**
182      * Resolve the given annotation-specified value,
183      * potentially containing placeholders and expressions.
184      */
185     @Nullable
186     private Object resolveStringValue(String value) {
187         if (this.configurableBeanFactory == null) {
188             return value;
189         }
190         String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
191         BeanExpressionResolver exprResolver = 
192                 this.configurableBeanFactory.getBeanExpressionResolver();
193         if (exprResolver == null || this.expressionContext == null) {
194             return value;
195         }
196         return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
197     }
198 
199     /**
200      * Resolve the given parameter type and value name into an argument value.
201      * @param name the name of the value being resolved
202      * @param parameter the method parameter to resolve to an argument value
203      * (pre-nested in case of a {@link java.util.Optional} declaration)
204      * @param request the current request
205      * @return the resolved argument (may be {@code null})
206      * @throws Exception in case of errors
207      */
208     @Nullable
209     protected abstract Object resolveName(String name, MethodParameter parameter, 
210                                         NativeWebRequest request)
211                                         throws Exception;
212 
213     /**
214      * Invoked when a named value is required, but {@link #resolveName(String, MethodParameter, 
215      * NativeWebRequest)}
216      * returned {@code null} and there is no default value. Subclasses typically throw an exception
217      *  in this case.
218      * @param name the name for the value
219      * @param parameter the method parameter
220      * @param request the current request
221      * @since 4.3
222      */
223     protected void handleMissingValue(String name, MethodParameter parameter, 
224             NativeWebRequest request) throws Exception {
225 
226         handleMissingValue(name, parameter);
227     }
228 
229     /**
230      * Invoked when a named value is required, but {@link #resolveName(String, MethodParameter, 
231      * NativeWebRequest)}
232      * returned {@code null} and there is no default value. Subclasses typically throw an exception 
233      * in this case.
234      * @param name the name for the value
235      * @param parameter the method parameter
236      */
237     protected void handleMissingValue(String name, MethodParameter parameter) 
238             throws ServletException {
239         throw new ServletRequestBindingException("Missing argument '" + name +
240             "' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
241     }
242 
243     /**
244      * A {@code null} results in a {@code false} value for {@code boolean}s or an exception 
245      * for other primitives.
246      */
247     @Nullable
248     private Object handleNullValue(String name, @Nullable Object value, Class<?> paramType) {
249         if (value == null) {
250             if (Boolean.TYPE.equals(paramType)) {
251                 return Boolean.FALSE;
252             }
253             else if (paramType.isPrimitive()) {
254     throw new IllegalStateException("Optional " + paramType.getSimpleName() 
255     + " parameter '" + name +
256     "' is present but cannot be translated into a null value due to being declared as a " +
257     "primitive type. Consider declaring it as object wrapper for the corresponding primitive type.");
258             }
259         }
260         return value;
261     }
262 
263     /**
264      * Invoked after a value is resolved.
265      * @param arg the resolved argument value
266      * @param name the argument name
267      * @param parameter the argument parameter type
268      * @param mavContainer the {@link ModelAndViewContainer} (may be {@code null})
269      * @param webRequest the current request
270      */
271     protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
272             @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
273             // 对解析后的参数值进行处理
274             // 当前实现是一个空方法,不过具体实现子类可以覆盖实现该方法以实现逻辑定制
275     }
276 
277 
278     /**
279      * Represents the information about a named value, including name, whether it's required and
280      *  a default value.
281      * 一个关于命名值的元数据描述类,包含如下信息 : 
282      * 1. 原始命名字符串(可能是一个需要求值的表达式)
283      * 2. 该参数是否必要
284      * 3. 该参数的参数值缺失时要应用的缺省值字符串
285      */
286     protected static class NamedValueInfo {
287 
288         private final String name;
289 
290         private final boolean required;
291 
292         @Nullable
293         private final String defaultValue;
294 
295         public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {
296             this.name = name;
297             this.required = required;
298             this.defaultValue = defaultValue;
299         }
300     }
301 
302 }
AbstractNamedValueMethodArgumentResolver#resolveArgument

handler方法参数值的验证 MethodValidationInterceptor

RequestMappingHandlerAdapter#invokeHandlerMethod==> ServletInvocableHandlerMethod#invokeAndHandle ==> InvocableHandlerMethod#invokeForRequest==> InvocableHandlerMethod#doInvoke 方法里调用目标hanlder方法,相关源码:

 1     @Nullable
 2     protected Object doInvoke(Object... args) throws Exception {
 3        // 确保目标控制器方法是可以访问的 
 4         ReflectionUtils.makeAccessible(getBridgedMethod());
 5         try {
 6            // 调用目标控制器方法 
 7             return getBridgedMethod().invoke(getBean(), args);
 8         }
 9         catch (IllegalArgumentException ex) {
10            // 抛出参数格式错误导致的异常 
11             assertTargetBean(getBridgedMethod(), getBean(), args);
12             String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
13             throw new IllegalStateException(formatInvokeError(text, args), ex);
14         }
15         catch (InvocationTargetException ex) {
16             // Unwrap for HandlerExceptionResolvers ...
17           // 对 InvocationTargetException 异常的处理
18             Throwable targetException = ex.getTargetException();
19             if (targetException instanceof RuntimeException) {
20                 throw (RuntimeException) targetException;
21             }
22             else if (targetException instanceof Error) {
23                 throw (Error) targetException;
24             }
25             else if (targetException instanceof Exception) {
26                 throw (Exception) targetException;
27             }
28             else {
29                 throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
30             }
31         }
32     }
InvocableHandlerMethod#doInvoke

若控制器类或handler方法使用了参数验证功能(@Validated、@Valid、@Min等),则getBean获取到的将是目标对象的代理对象(代理对象通过AOP产生,相关逻辑在MethodValidationPostProcessor),该代理对象是MethodValidationInterceptor的实例,会在调用目标handler前后进行参数、返回值的验证。相关源码:

 1     @Override
 2     @SuppressWarnings("unchecked")
 3     public Object invoke(MethodInvocation invocation) throws Throwable {
 4         // Avoid Validator invocation on FactoryBean.getObjectType/isSingleton
 5         if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
 6             return invocation.proceed();
 7         }
 8 
 9         Class<?>[] groups = determineValidationGroups(invocation);
10 
11         // Standard Bean Validation 1.1 API
12         ExecutableValidator execVal = this.validator.forExecutables();
13         Method methodToValidate = invocation.getMethod();
14         Set<ConstraintViolation<Object>> result;
15 
16         try {
17             result = execVal.validateParameters(
18                     invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
19         }
20         catch (IllegalArgumentException ex) {
21             // Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
22             // Let's try to find the bridged method on the implementation class...
23             methodToValidate = BridgeMethodResolver.findBridgedMethod(
24                     ClassUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass()));
25             result = execVal.validateParameters(
26                     invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
27         }
28         if (!result.isEmpty()) {
29             throw new ConstraintViolationException(result);
30         }
31 
32         Object returnValue = invocation.proceed();
33 
34         result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);
35         if (!result.isEmpty()) {
36             throw new ConstraintViolationException(result);
37         }
38 
39         return returnValue;
40     }
MethodValidationInterceptor#invoke

调用handler方法本身

上述 InvocableHandlerMethod#doInvoke中,getBridgedMethod().invoke(getBean(), args) 调用会先触发层层方法拦截器,没有拦截逻辑异常或者其他分支的情况下,目标控制器方法会被最终调用。

handler方法返回值处理

与前面的参数解析绑定类似,由组合了多个HandlerMethodReturnValueHandler实现类对象的HandlerMethodReturnValueHandlerComposite对象完成返回值处理工作。相关源码:

 1   // ServletInvocableHandlerMethod 代码片段
 2     /**
 3      * Invoke the method and handle the return value through one of the
 4      * configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
 5      * @param webRequest the current request
 6      * @param mavContainer the ModelAndViewContainer for this request
 7      * @param providedArgs "given" arguments matched by type (not resolved)
 8      */
 9     public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
10             Object... providedArgs) throws Exception {
11 
12        // 调用目标控制器方法,这一过程主要做了以下两件事情 :
13        // 1. 从请求分析参数值并转换成可用于目标控制器方法的结构和类型
14        // 2. 调用开发人员提供的目标控制器方法
15         Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
16        // 现在 returnValue 是目标控制器方法执行的返回结果,之所以使用 Object 类型接收,
17        // 是因为框架并不清楚开发人员会设计什么样的返回类型
18        
19        // 获取控制器方法上的注解 @ResponseStatus 信息,注解 @ResponseStatus 会有两个属性 :
20        // status 和 reason,这里就是参考这两个属性对响应对象做处理 :
21        // 情况1 : status 不为 null, reason 有内容(不为null,也不为空字符串), response.setStatus(status)
22        // 情况2 : status 不为 null, reason 无内容(为null,或者空字符串), response.sendError(status,reason)
23        // 这两种情况下也会设置请求属性 : <RESPONSE_STATUS_ATTRIBUTE,status>, 重定向的话会使用该信息。
24        // 该过程并没有基于 returnValue 的任何动作。
25        // 如果 status 为 null ,可以认为这一步什么都没做
26         setResponseStatus(webRequest);
27 
28 
29        // 开始基于结合 @ResponseStatus 注解和返回值 returnValue 进行处理的逻辑       
30         if (returnValue == null) {
31            // 控制器方法的返回值是 null 
32             if (isRequestNotModified(webRequest) 
33                 || getResponseStatus() != null 
34                 || mavContainer.isRequestHandled()) {
35              // 针对如下三种情况组合标记请求已经被完全处理 并且 当前方法返回 :
36              // 1. 目标控制器方法无返回值 + 请求资源没有改变
37              // 2. 目标控制器方法无返回值 + 目标控制器方法注解  @ResponseStatus 属性 status 不为 null
38              // 3. 目标控制器方法无返回值 + mavContainer 中请求处理完成标记已经被设置
39                 mavContainer.setRequestHandled(true);
40                 return;
41             }
42         }
43         else if (StringUtils.hasText(getResponseStatusReason())) {
44           // 目标控制器方法有返回值 +  目标控制器方法注解  @ResponseStatus 属性 reason 有内容
45           // 这种情况下也将请求标记为已经处理完成,并且 当前方法返回
46             mavContainer.setRequestHandled(true);
47             return;
48         }
49 
50        //  结合 @ResponseStatus 注解和返回值 returnValue  考虑之后,发现需要进一步处理返回值,
51        // 此时先将请求标记为尚未处理完成,然后使用 this.returnValueHandlers 进行处理
52        
53        // 将请求处理完成标记设置为 false
54         mavContainer.setRequestHandled(false);
55        // 断言 this.returnValueHandlers 不能为空,因为接下来要处理返回值的动作需要使用到它们        
56         Assert.state(this.returnValueHandlers != null, "No return value handlers");
57         try {
58           // this.returnValueHandlers 是一个 HandlerMethodReturnValueHandlerComposite 对象
59             this.returnValueHandlers.handleReturnValue(
60                     returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
61         }
62         catch (Exception ex) {
63             if (logger.isTraceEnabled()) {
64                 logger.trace(formatErrorForReturnValue(returnValue), ex);
65             }
66             throw ex;
67         }
68     }
ServletInvocableHandlerMethod#invokeAndHandle

同样地,SpringMVC框架提供了很多HandlerMethodReturnValueHandler的实现类来处理返回值:

包装返回结果 : 从ModelAndViewContainer对象构造ModelAndView对象

前面整体流程 RequestMappingHandlerAdapter#invodeHandlerMethod 中最后调用 RequestMappingHandlerAdapter#getModelAndView 方法,后者的相关源码:

 1     @Nullable
 2     private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
 3             ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
 4 
 5         modelFactory.updateModel(webRequest, mavContainer);
 6         if (mavContainer.isRequestHandled()) {
 7            // 如果  mavContainer.isRequestHandled() 为真,说明上一篇中介绍的逻辑已经将请求处理完了,
 8            // 所以这里返回 null,这里的返回 null 有两点意思需要指明 :
 9            // 1. 当前方法以下构造 ModelAndView 的主逻辑相当于不用执行了;
10            // 2. 遇到 null ModelAndView,调用者会认为请求已经处理完成,不会再继续处理该请求;
11             return null;
12         }
13         
14         // 逻辑能走到这里的话,说明之前的处理逻辑并未将请求处理完成,所以现在需要根据之前的执行结果
15         // 构造 ModelAndView 对象,这是留给调用者 DispatcherServlet 用于解析和渲染视图的数据载体。
16         ModelMap model = mavContainer.getModel();
17         ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
18         if (!mavContainer.isViewReference()) {
19             mav.setView((View) mavContainer.getView());
20         }
21         if (model instanceof RedirectAttributes) {
22           // 针对重定向情况的调整  
23             Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
24             HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
25             if (request != null) {
26                 RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
27             }
28         }
29         return mav;
30     }
View Code

ServletInvocableHandlerMethod#invokeAndHandle对hanlder返回值处理过程中,若基于上下文和组件认为请求处理已经完成,则会将mavContainer.requestHandled属性设置为true,否则设为false以将控制权交给调用者,继续请求处理过程。可见,当为false时,这里会创建ModelAndView对象,以供上层DispatcherServlet进行视图解析和渲染。

 

 

HandlerMapping

接口,用于定义request和handler间的对应关系,将依据此确定request由哪个handler处理。springframework默认有两种HandlerMapping的实现方式,当application context中没有HandlerMapping Bean时默认采用前者,但实际上通常会将后者注册为Bean。

BeanNameUrlHandlerMapping:map from URLs to beans with names that start with a slash ("/"),即将url映射到与url同名的Bean上

RequestMappingHandlerMapping:Creates RequestMappingInfo instances from type and method-level @RequestMapping annotations in @Controller classes,即建立url与handler间的对应关系。默认为此

在程序启动时springframework会自动扫描并建立该对应关系。对应关系的建立过程分析可参阅:https://blog.csdn.net/andy_zhang2007/article/details/99414968

可以重写该类的方法来自定义关联规则,如当前项目引入其他项目的Feign Client时被引项目的handler也会被当前项目的HandlerMapping识别成当前项目的接口,可通过重写规避:

@Configuration
@ConditionalOnClass({ Feign.class })
public class FeignConfiguration {// 解决@FeignClient中的@RequestMapping也被当前项目识别成Handler的问题

    @Bean
    public WebMvcRegistrations feignWebRegistrations() {
        return new WebMvcRegistrations() {
            @Override
            public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
                return new FeignRequestMappingHandlerMapping();
            }
        };
    }

    private static class FeignRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
        @Override
        protected boolean isHandler(Class<?> beanType) {
            return super.isHandler(beanType) && !AnnotatedElementUtils.hasAnnotation(beanType, FeignClient.class);
        }
    }
}
View Code

  

DispatcherServlet

(官方文档:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web.html#mvc-servlet-sequence

继承HttpServlet,传统java Servlet项目中每个文件为一个Servlet,而这里一个DispatcherServlet类作为一个Servlet,起网关的作用,路由到与请求url相应的handler。

详细逻辑可参阅其doDispatch方法,相关源码:

  1     /**
  2      * Process the actual dispatching to the handler.
  3      * The handler will be obtained by applying the servlet's HandlerMappings in order.
  4      * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
  5      * to find the first that supports the handler class.
  6      * All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
  7      * themselves to decide which methods are acceptable.
  8      * @param request current HTTP request
  9      * @param response current HTTP response
 10      * @throws Exception in case of any kind of processing failure
 11      */
 12     protected void doDispatch(HttpServletRequest request, HttpServletResponse response) 
 13         throws Exception {
 14         HttpServletRequest processedRequest = request;
 15         // 用于记录针对该请求的请求处理器 handler 以及有关的 HandlerInteceptor , 封装成一个 
 16         // HandlerExecutionChain, 在随后的逻辑中会被使用
 17         HandlerExecutionChain mappedHandler = null;
 18         // 用于标记当前请求是否是一个文件上传请求
 19         boolean multipartRequestParsed = false;
 20 
 21         WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
 22 
 23         try {
 24             ModelAndView mv = null;
 25             Exception dispatchException = null;
 26 
 27             try {
 28                 // 检查请求是否文件上传请求  
 29                 processedRequest = checkMultipart(request);
 30                 // 如果 processedRequest 和  request 不同,说明检测到了文件上传请求过程中
 31                 // 才会出现的内容,现在把这个信息记录在 multipartRequestParsed
 32                 multipartRequestParsed = (processedRequest != request);
 33 
 34                 // Determine handler for the current request.
 35                 // 这里根据请求自身,和 DispatcherServlet 的 handlerMappings 查找能够处理该请求的
 36                 // handler, 记录在 mappedHandler , 具体返回的类型是 HandlerExecutionChain ,
 37                 // 相当于 n 个 HandlerInterceptor + 1 个 handler 。
 38                 mappedHandler = getHandler(processedRequest);
 39                 if (mappedHandler == null) {
 40                     // 如果没有找到可以处理该请求的 handler , 这里直接交给 noHandlerFound 处理,
 41                     // 根据配置决定 抛出异常 或者返回 404
 42                     noHandlerFound(processedRequest, response);
 43                     return;
 44                 }
 45 
 46                 // Determine handler adapter for the current request.
 47                 // 根据所找到的 handler 和 DispatcherServlet 的 handlerAdapters 看看哪个 handlerAdapter
 48                 // 可以执行这个 handler。
 49                 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
 50 
 51                 // Process last-modified header, if supported by the handler.
 52                 String method = request.getMethod();
 53                 boolean isGet = "GET".equals(method);
 54                 if (isGet || "HEAD".equals(method)) {
 55                     long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
 56                     if (new ServletWebRequest(request, response).checkNotModified(lastModified) 
 57                         && isGet) {
 58                         // 如果是 GET/HEAD 模式,并且发现资源从上次访问到现在没被修改过,则直接返回
 59                         return;
 60                     }
 61                 }
 62 
 63                 // 执行 mappedHandler 所有 HandlerInteceptor 的 preHandle 方法,
 64                 // 如果有执行失败,在这里直接返回不再继续处理该请求
 65                 if (!mappedHandler.applyPreHandle(processedRequest, response)) {
 66                     return;
 67                 }
 68 
 69                 // Actually invoke the handler.
 70                 // 现在才真正由所找到的 HandlerAdapter  执行 所对应的 handler,
 71                 // 返回结果是一个 ModelAndView 对象 mv, 或者 null
 72                 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
 73 
 74                 if (asyncManager.isConcurrentHandlingStarted()) {
 75                     return;
 76                 }
 77 
 78                 // 如果上面获取的 ModelAndView 对象 mv 不为 null, 并且没有含有 view,
 79                 // 并且配置了缺省 view,这里应用缺省 view
 80                 applyDefaultViewName(processedRequest, mv);
 81               
 82                 // 执行 mappedHandler 所有 HandlerInteceptor 的 postHandle 方法,
 83                 mappedHandler.applyPostHandle(processedRequest, response, mv);
 84             }
 85             catch (Exception ex) {
 86                 dispatchException = ex;
 87             }
 88             catch (Throwable err) {
 89                 // As of 4.3, we're processing Errors thrown from handler methods as well,
 90                 // making them available for @ExceptionHandler methods and other scenarios.
 91                 dispatchException = new NestedServletException("Handler dispatch failed", err);
 92             }
 93             // 处理 handler 结果 ModelAndView mv
 94             // 1. 根据 view 名称 或者 异常找到最终要使用的 view
 95             // 2. 渲染 view
 96             // 3. 执行 mappedHandler 所有 HandlerInteceptor 的 afterCompletion 方法
 97             processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
 98         }
 99         catch (Exception ex) {
100             // 异常情况下执行 mappedHandler 所有 HandlerInteceptor 的 afterCompletion 方法
101             triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
102         }
103         catch (Throwable err) {
104             // 异常情况下执行 mappedHandler 所有 HandlerInteceptor 的 afterCompletion 方法
105             triggerAfterCompletion(processedRequest, response, mappedHandler,
106                     new NestedServletException("Handler processing failed", err));
107         }
108         finally {
109             if (asyncManager.isConcurrentHandlingStarted()) {
110                 // Instead of postHandle and afterCompletion
111                 if (mappedHandler != null) {
112                     mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
113                 }
114             }
115             else {
116                 // Clean up any resources used by a multipart request.
117                 if (multipartRequestParsed) {
118                     cleanupMultipart(processedRequest);
119                 }
120             }
121         }
122     }
DispatcherServlet#doDispatch

doDispatch主逻辑详细描述(转):

 1 Spring MVC前置控制器DispatcherServlet对请求进行处理的主要逻辑位于其方法doDispatch,如下 :
 2 
 3 1 找到可以处理该请求的 Handler
 4  找到的 Handler 以 HandlerExecutionChain 形式封装
 5  任务执行主要代码行 : #getHandler(processedRequest)
 6  如果没有能处理该请求的 Handler,则抛出异常或者返回404
 7   任务执行主要代码行 : noHandlerFound(processedRequest, response)
 8 
 9 2 找到能执行该 Handler 的 HandlerAdapter 用于调用该Handler
10  任务执行主要代码行 : HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler())
11  如果没有找到合适的HandlerAdapter,则该方法抛出异常ServletException,带相应信息描述。
12 
13 3 通过找到的HandlerAdapter 处理相应的请求
14  对当前Handler的前置处理
15   任务执行主要代码行 : mappedHandler.applyPreHandle(processedRequest, response)
16   如果某个HandlerInterceptor#preHandle返回false,则doDispatch调用triggerAfterCompletion然后return
17  通过HandlerAdapter真正处理当前Handler
18   任务执行主要代码行 : mv = ha.handle(processedRequest, response, mappedHandler.getHandler())
19  必要时应用缺省视图
20   任务执行主要代码行 : applyDefaultViewName(processedRequest, mv)
21  对当前Handler的后置处理
22   任务执行主要代码行 : mappedHandler.applyPostHandle(processedRequest, response, mv)
23 
24 以上流程涉及视图名称的获取和设置,但尚不涉及从视图名称到视图的解析逻辑。
25  上面的执行要么成功返回一个ModelAndView mv,要么错误产生异常dispatchException。
26  任务执行主要代码行 : processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)
27  如果该任务执行出现异常/错误,则doDispatch会携带相应异常/错误信息调用triggerAfterCompletion并return。
28 
29 4 处理Handler执行结果
30  如果是dispatchException的情况,则根据异常构建相应的一个ModelAndView对象记到mv然后继续。
31   exception是ModelAndViewDefiningException的情况直接从其中获取ModelAndView到mv;
32   要么调用processHandlerException(request, response, handler, exception)获取一个ModelAndView到mv;
33    这里遍历所登记的HandlerExceptionResolver bean尝试从异常解析出相应的ModelAndView对象;
34    也可能调用结果为null;
35  渲染视图
36   任务执行主要代码行 : render(mv, request, response)
37   Locale应用设置到response;
38   根据视图名称解析视图;
39    如果解析不到视图,则抛出异常ServletException携带相应信息描述。
40   真正的渲染视图;
41    任务执行主要代码行 : view.render(mv.getModelInternal(), request, response)
42    redirect/forward处理在这里完成。
View Code

https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web.html#mvc-servlet-sequence

The DispatcherServlet processes requests as follows:

  • The WebApplicationContext is searched for and bound in the request as an attribute that the controller and other elements in the process can use. It is bound by default under the DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE key.

  • The locale resolver is bound to the request to let elements in the process resolve the locale to use when processing the request (rendering the view, preparing data, and so on). If you do not need locale resolving, you do not need the locale resolver.

  • The theme resolver is bound to the request to let elements such as views determine which theme to use. If you do not use themes, you can ignore it.

  • If you specify a multipart file resolver, the request is inspected for multiparts. If multiparts are found, the request is wrapped in a MultipartHttpServletRequest for further processing by other elements in the process. See Multipart Resolver for further information about multipart handling.

  • An appropriate handler is searched for. If a handler is found, the execution chain associated with the handler (preprocessors, postprocessors, and controllers) is run to prepare a model for rendering. Alternatively, for annotated controllers, the response can be rendered (within the HandlerAdapter) instead of returning a view.

  • If a model is returned, the view is rendered. If no model is returned (maybe due to a preprocessor or postprocessor intercepting the request, perhaps for security reasons), no view is rendered, because the request could already have been fulfilled.

The HandlerExceptionResolver beans declared in the WebApplicationContext are used to resolve exceptions thrown during request processing. Those exception resolvers allow customizing the logic to address exceptions. See Exceptions for more details.

 

为request寻找并执行对应handler的主要过程:

根据HandlerMapping获取handler with interceptors,即HandlerExecutionChain实例。support该handler的HandlerMapping可能有多个,按顺序遍历,成功获取到HandlerExecutionChain实例即止(获取的过程HandlerExecutionChain实质上是调用AbstractUrlHandlerMapping.lookupHandler,即根据url找对应的hanlder)。

    @Nullable
    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;
    }
View Code

根据handler获取处理该handler的HandlerAdaptor实例,support该handler的HandlerAdaptor可能有多个,按顺序遍历HandlerAdaptor列表取第一个support者。

    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");
    }
View Code

根据HandlerExecutionChain实例执行所有Interceptor的preHandle方法

根据HandlerAdaptor实例执行目标handler方法

根据HandlerExecutionChain实例执行所有Interceptor的postHandle方法

对执行结果进行处理(processDispatchResult),包括:

1、处理异常(processHandlerException)或解析视图(render),二选一;

2、执行各Interceptor的afterCompletion方法

 

可见,DispatcherServlet通过调用HandlerMapping.getHandler方法获取处理当前请求的handler(HandlerMapping可能有多个,按优先级依次尝试通过HandlerMapping获取handler,成功即止),获取到的不是纯handler,而是HandlerExecutionChain,其封装了handler及针对hanlder的拦截器。DispatcherServlet会依次调用各拦截器方法再调用目标handler方法。

A handler will always be wrapped in a HandlerExecutionChain instance, optionally accompanied by some HandlerInterceptor instances. The DispatcherServlet will first call each HandlerInterceptor's preHandle method in the given order, finally invoking the handler itself if all preHandle methods have returned true.
View Code

默认情况下,对于request如果没有找到与url对应的controller方法,则会当做请求静态资源来处理(对应的Handler、HandlerAdapter分别为ResourceHttpRequestHandler、HttpRequestHandlerAdapter来处理),尝试从配置的各static resouce路径下获取该url对应的资源。

 

题外话:

spring-webmvc中有个核心Servlet,即DispatcherServle,通常Servlet都是运行在外部Servlet容器(如Tomcat)中的,但是Spring如果要编写单元测试,对这个DispatcherServle进行测试,或者其他组件,会不会启动Tomcat呢?答案是不会。或者说,不借助外部Servlet容器,有没有办法手动启用这个Servlet?可以的。

方案:既然外部Servlet容器运行时会调用Servlet的service方法,那我们手动调用即可,至于参数,可用Spring提供的Mock Request和Response。两个示例:

class WebMvcTest1 {
    @Controller
    static class UserController {
        @GetMapping("getUser")
        @ResponseBody
        public String getUser() {
            return "user";
        }
    }

    private static DispatcherServlet initServlet(final Class<?> controllerClass) throws ServletException {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.setServletContext(new MockServletContext());
        ctx.register(UserController.class);
        ctx.refresh();
        DispatcherServlet servlet = new DispatcherServlet(ctx);
        return servlet;
    }

    public static void main(String[] args) throws ServletException, IOException {
        MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest("GET", "/getUser");
        MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse();
        DispatcherServlet servlet = initServlet(UserController.class);
        servlet.init(new MockServletConfig());
        servlet.service(mockHttpServletRequest, mockHttpServletResponse);
    }
}

class WebMvcTest2 {
    @Target({ ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    static @interface LogMethod {
    }

    @LogMethod
    @Controller
    public static class UserController {
        @GetMapping("getUser")
        @ResponseBody
        public String getUser() {
            return "user";
        }
    }

    private static DispatcherServlet initServlet(final Class<?> controllerClass) throws ServletException {
        DispatcherServlet dispatcherServlet = new DispatcherServlet() {
            @Override
            protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
                GenericWebApplicationContext wac = new GenericWebApplicationContext();
                wac.registerBeanDefinition("controller", new RootBeanDefinition(controllerClass));
                DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
                autoProxyCreator.setProxyTargetClass(true);
                autoProxyCreator.setBeanFactory(wac.getBeanFactory());
                wac.getBeanFactory().addBeanPostProcessor(autoProxyCreator);
                Pointcut pointcut = new AnnotationMatchingPointcut(LogMethod.class);
                DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, new MethodBeforeAdvice() {
                    @Override
                    public void before(Method method, Object[] args, Object target) throws Throwable {
                        System.out.println("进入方法" + method);
                    }
                });
                wac.getBeanFactory().registerSingleton("advisor", advisor);
                wac.refresh();
                return wac;
            }
        };
        dispatcherServlet.init(new MockServletConfig());
        return dispatcherServlet;
    }

    public static void main(String[] args) throws ServletException, IOException {

        DispatcherServlet dispatcherServlet = initServlet(UserController.class);
        MockHttpServletRequest request = new MockHttpServletRequest("GET", "/getUser");
        MockHttpServletResponse response = new MockHttpServletResponse();
        dispatcherServlet.service(request, response);
        System.out.println(response.getContentAsString());
    }
}
View Code

可参阅 Spring Web模块是怎样进行单元测试的

 

默认Resolvers

默认情况下,如果开发者没有自定义指定,则DispatcherServlet会自动初始化所用的MultipartResolver、ThemeResolver、HandlerMappings、HandlerAdapters、HandlerExceptionResolvers、ReqeustToViewNameTranslator、ViewResolvers等,具体见内部的对应init方法。相关源码:

  1     /**
  2      * This implementation calls {@link #initStrategies}.
  3      */
  4     @Override
  5     protected void onRefresh(ApplicationContext context) {
  6         initStrategies(context);
  7     }
  8 
  9 
 10 
 11     /**
 12      * Initialize the strategy objects that this servlet uses.
 13      * <p>May be overridden in subclasses in order to initialize further strategy objects.
 14      */
 15     protected void initStrategies(ApplicationContext context) {
 16         initMultipartResolver(context);
 17         initLocaleResolver(context);
 18         initThemeResolver(context);
 19         initHandlerMappings(context);
 20         initHandlerAdapters(context);
 21         initHandlerExceptionResolvers(context);
 22         initRequestToViewNameTranslator(context);
 23         initViewResolvers(context);
 24         initFlashMapManager(context);
 25     }
 26     /**
 27      * Initialize the HandlerMappings used by this class.
 28      * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
 29      * we default to BeanNameUrlHandlerMapping.
 30      */
 31     private void initHandlerMappings(ApplicationContext context) {
 32         this.handlerMappings = null;
 33 
 34         if (this.detectAllHandlerMappings) {
 35             // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
 36             Map<String, HandlerMapping> matchingBeans =
 37                     BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
 38             if (!matchingBeans.isEmpty()) {
 39                 this.handlerMappings = new ArrayList<>(matchingBeans.values());
 40                 // We keep HandlerMappings in sorted order.
 41                 AnnotationAwareOrderComparator.sort(this.handlerMappings);
 42             }
 43         }
 44         else {
 45             try {
 46                 HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
 47                 this.handlerMappings = Collections.singletonList(hm);
 48             }
 49             catch (NoSuchBeanDefinitionException ex) {
 50                 // Ignore, we'll add a default HandlerMapping later.
 51             }
 52         }
 53 
 54         // Ensure we have at least one HandlerMapping, by registering
 55         // a default HandlerMapping if no other mappings are found.
 56         if (this.handlerMappings == null) {
 57             this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
 58             if (logger.isTraceEnabled()) {
 59                 logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
 60                         "': using default strategies from DispatcherServlet.properties");
 61             }
 62         }
 63     }
 64 
 65     /**
 66      * Initialize the HandlerAdapters used by this class.
 67      * <p>If no HandlerAdapter beans are defined in the BeanFactory for this namespace,
 68      * we default to SimpleControllerHandlerAdapter.
 69      */
 70     private void initHandlerAdapters(ApplicationContext context) {
 71         this.handlerAdapters = null;
 72 
 73         if (this.detectAllHandlerAdapters) {
 74             // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
 75             Map<String, HandlerAdapter> matchingBeans =
 76                     BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
 77             if (!matchingBeans.isEmpty()) {
 78                 this.handlerAdapters = new ArrayList<>(matchingBeans.values());
 79                 // We keep HandlerAdapters in sorted order.
 80                 AnnotationAwareOrderComparator.sort(this.handlerAdapters);
 81             }
 82         }
 83         else {
 84             try {
 85                 HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
 86                 this.handlerAdapters = Collections.singletonList(ha);
 87             }
 88             catch (NoSuchBeanDefinitionException ex) {
 89                 // Ignore, we'll add a default HandlerAdapter later.
 90             }
 91         }
 92 
 93         // Ensure we have at least some HandlerAdapters, by registering
 94         // default HandlerAdapters if no other adapters are found.
 95         if (this.handlerAdapters == null) {
 96             this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
 97             if (logger.isTraceEnabled()) {
 98                 logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
 99                         "': using default strategies from DispatcherServlet.properties");
100             }
101         }
102     }
DispatcherServlet#initStrategies

其原理是:先尝试从容器中找用户注册的Resolver、Handler、Converter等,若未找到,则根据默认stragies来注册各Resolver。这里的strategies是从弄个DispatcherServlet同目录下的一个DispatcherServlet.properties文件里读取内容,该文件指定了默认采用的各种Resolver。内容如下:

 1 # Default implementation classes for DispatcherServlet's strategy interfaces.
 2 # Used as fallback when no matching beans are found in the DispatcherServlet context.
 3 # Not meant to be customized by application developers.
 4 
 5 org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
 6 
 7 org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
 8 
 9 org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
10     org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
11 
12 org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
13     org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
14     org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
15 
16 org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
17     org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
18     org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
19 
20 org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
21 
22 org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
23 
24 org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
DispatcherServlet.properties

DispatcherServlet默认检测注册的Bean

special beans detected by the DispatcherServlet:

HandlerMapping

HandlerAdaptter

HandlerExceptionResolver

ViewResolver:解析视图

LocaleResolver/LocaleContextResolver,包括:

Time Zone

Header Resolver

Cookie Resolver

Session Resolver

Locale Interceptor

ThemeResolver

MultipartResolver

FlashMapManager

开发者自定义实现这些Bean,Spring 容器会以开发者提供的Bean来替换默认的Bean。

 

HandlerExceptionResolver

处理handler执行的异常,会依次调用各HandlerExceptionResolver的resolveException方法,若有一个返回非null则结束。

只需要实现HandlerExceptionResolver接口并重写resolveException方法即可实现自定义的异常处理。

 

SpringMVC框架默认实现了四个Resolver:

(参考资料:https://blog.csdn.net/andy_zhang2007/article/details/88594280

四个Resolver的关系

 

四个Resolver

org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver

仅仅针对Handler类型为HandlerMethod中发生异常的情况,也就是@Controller组件类中的@RequestMapping方法中的异常。当这类异常交给ExceptionHandlerExceptionResolver解析时,它会首先查看发生异常的handler方法所在的controller类中是否有合适的@ExceptionHanlder方法,然后看所有的@ControllerAdvice类中是否有合适的@ExceptionHanlder方法,如果有,ExceptionHandlerExceptionResolver就会使用相应的方法处理该异常。源码:

  1 package org.springframework.web.servlet.mvc.method.annotation;
  2 
  3 // 省略 import 行
  4 
  5 /**
  6  * An {@link AbstractHandlerMethodExceptionResolver} that resolves exceptions
  7  * through {@code @ExceptionHandler} methods.
  8  *
  9  * <p>Support for custom argument and return value types can be added via
 10  * {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers}.
 11  * Or alternatively to re-configure all argument and return value types use
 12  * {@link #setArgumentResolvers} and {@link #setReturnValueHandlers(List)}.
 13  *
 14  * @author Rossen Stoyanchev
 15  * @author Juergen Hoeller
 16  * @since 3.1
 17  */
 18 public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
 19         implements ApplicationContextAware, InitializingBean {
 20 
 21     @Nullable
 22     private List<HandlerMethodArgumentResolver> customArgumentResolvers;
 23 
 24     @Nullable
 25     private HandlerMethodArgumentResolverComposite argumentResolvers;
 26 
 27     @Nullable
 28     private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
 29 
 30     @Nullable
 31     private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
 32 
 33     // 一组 HttpMessageConverter
 34     // 供能够直接输出响应体的 HandlerMethodReturnValueHandler 使用
 35     private List<HttpMessageConverter<?>> messageConverters;
 36 
 37     private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
 38 
 39     //  将异常处理控制器方法返回值转换成响应体时的Advice逻辑,可以做一些修饰,变形转换等等
 40     private final List<Object> responseBodyAdvice = new ArrayList<>();
 41 
 42     @Nullable
 43     private ApplicationContext applicationContext;
 44 
 45     // 保存发生异常的控制器方法所在的控制器组件中的@ExceptionHandler异常处理控制器方法
 46     private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =
 47             new ConcurrentHashMap<>(64);
 48 
 49     // 缓存@ControllerAdvice bean组件中的@ExceptionHandler异常处理控制器方法
 50     private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> 
 51             exceptionHandlerAdviceCache =    new LinkedHashMap<>();
 52 
 53 
 54     public ExceptionHandlerExceptionResolver() {
 55        // 此构造函数方法体主要是初始化属性 this.messageConverters, 注意其初始值 :
 56        // 1. ByteArrayHttpMessageConverter 二进制字节流
 57        // 2. StringHttpMessageConverter, ISO8859-1 ,字符串
 58        // 3. SourceHttpMessageConverter , DOMSource/SAXSource/StAXSource/StreamSource等
 59        // 4. AllEncompassingFormHttpMessageConverter ,UTF-8, 用于读写表单数据和写multipart数据,
 60        // 在FormHttpMessageConverter上扩展,增加了 XML/JSON转换能力
 61         StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
 62         stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316
 63 
 64         this.messageConverters = new ArrayList<>();
 65         this.messageConverters.add(new ByteArrayHttpMessageConverter());
 66         this.messageConverters.add(stringHttpMessageConverter);
 67         try {
 68             this.messageConverters.add(new SourceHttpMessageConverter<>());
 69         }
 70         catch (Error err) {
 71             // Ignore when no TransformerFactory implementation is available
 72         }
 73         this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
 74     }
 75 
 76 
 77     /**
 78      * Provide resolvers for custom argument types. Custom resolvers are ordered
 79      * after built-in ones. To override the built-in support for argument
 80      * resolution use {@link #setArgumentResolvers} instead.
 81      */
 82     public void setCustomArgumentResolvers(@Nullable List<HandlerMethodArgumentResolver> 
 83         argumentResolvers) {
 84         this.customArgumentResolvers= argumentResolvers;
 85     }
 86 
 87     /**
 88      * Return the custom argument resolvers, or {@code null}.
 89      */
 90     @Nullable
 91     public List<HandlerMethodArgumentResolver> getCustomArgumentResolvers() {
 92         return this.customArgumentResolvers;
 93     }
 94 
 95     /**
 96      * Configure the complete list of supported argument types thus overriding
 97      * the resolvers that would otherwise be configured by default.
 98      */
 99     public void setArgumentResolvers(
100             @Nullable List<HandlerMethodArgumentResolver> argumentResolvers) {
101         if (argumentResolvers == null) {
102             this.argumentResolvers = null;
103         }
104         else {
105             this.argumentResolvers = new HandlerMethodArgumentResolverComposite();
106             this.argumentResolvers.addResolvers(argumentResolvers);
107         }
108     }
109 
110     /**
111      * Return the configured argument resolvers, or possibly {@code null} if
112      * not initialized yet via {@link #afterPropertiesSet()}.
113      */
114     @Nullable
115     public HandlerMethodArgumentResolverComposite getArgumentResolvers() {
116         return this.argumentResolvers;
117     }
118 
119     /**
120      * Provide handlers for custom return value types. Custom handlers are
121      * ordered after built-in ones. To override the built-in support for
122      * return value handling use {@link #setReturnValueHandlers}.
123      */
124     public void setCustomReturnValueHandlers(
125         @Nullable List<HandlerMethodReturnValueHandler> returnValueHandlers) {
126         this.customReturnValueHandlers = returnValueHandlers;
127     }
128 
129     /**
130      * Return the custom return value handlers, or {@code null}.
131      */
132     @Nullable
133     public List<HandlerMethodReturnValueHandler> getCustomReturnValueHandlers() {
134         return this.customReturnValueHandlers;
135     }
136 
137     /**
138      * Configure the complete list of supported return value types thus
139      * overriding handlers that would otherwise be configured by default.
140      */
141     public void setReturnValueHandlers(
142         @Nullable List<HandlerMethodReturnValueHandler> returnValueHandlers) {
143         if (returnValueHandlers == null) {
144             this.returnValueHandlers = null;
145         }
146         else {
147             this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
148             this.returnValueHandlers.addHandlers(returnValueHandlers);
149         }
150     }
151 
152     /**
153      * Return the configured handlers, or possibly {@code null} if not
154      * initialized yet via {@link #afterPropertiesSet()}.
155      */
156     @Nullable
157     public HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() {
158         return this.returnValueHandlers;
159     }
160 
161     /**
162      * Set the message body converters to use.
163      * <p>These converters are used to convert from and to HTTP requests and responses.
164      */
165     public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
166         this.messageConverters = messageConverters;
167     }
168 
169     /**
170      * Return the configured message body converters.
171      */
172     public List<HttpMessageConverter<?>> getMessageConverters() {
173         return this.messageConverters;
174     }
175 
176     /**
177      * Set the {@link ContentNegotiationManager} to use to determine requested media types.
178      * If not set, the default constructor is used.
179      */
180     public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
181         this.contentNegotiationManager = contentNegotiationManager;
182     }
183 
184     /**
185      * Return the configured {@link ContentNegotiationManager}.
186      */
187     public ContentNegotiationManager getContentNegotiationManager() {
188         return this.contentNegotiationManager;
189     }
190 
191     /**
192      * Add one or more components to be invoked after the execution of a controller
193      * method annotated with {@code @ResponseBody} or returning {@code ResponseEntity}
194      * but before the body is written to the response with the selected
195      * {@code HttpMessageConverter}.
196      */
197     public void setResponseBodyAdvice(@Nullable List<ResponseBodyAdvice<?>> responseBodyAdvice) {
198         this.responseBodyAdvice.clear();
199         if (responseBodyAdvice != null) {
200             this.responseBodyAdvice.addAll(responseBodyAdvice);
201         }
202     }
203 
204     @Override
205     public void setApplicationContext(@Nullable ApplicationContext applicationContext) {
206         this.applicationContext = applicationContext;
207     }
208 
209     @Nullable
210     public ApplicationContext getApplicationContext() {
211         return this.applicationContext;
212     }
213 
214 
215    // InitializingBean 接口定义的初始化方法
216     @Override
217     public void afterPropertiesSet() {
218         // Do this first, it may add ResponseBodyAdvice beans
219         initExceptionHandlerAdviceCache();
220 
221         if (this.argumentResolvers == null) {
222             List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
223             this.argumentResolvers = 
224                 new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
225         }
226         if (this.returnValueHandlers == null) {
227             List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
228             this.returnValueHandlers = 
229                 new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
230         }
231     }
232 
233     private void initExceptionHandlerAdviceCache() {
234         if (getApplicationContext() == null) {
235             return;
236         }
237 
238        // 从容器中找到所有使用了注解 @ControllerAdvice 的 bean 组件并排序 
239         List<ControllerAdviceBean> adviceBeans = 
240             ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
241         AnnotationAwareOrderComparator.sort(adviceBeans);
242 
243         
244         for (ControllerAdviceBean adviceBean : adviceBeans) {
245             Class<?> beanType = adviceBean.getBeanType();
246             if (beanType == null) {
247                 throw new IllegalStateException(
248                     "Unresolvable type for ControllerAdviceBean: " + adviceBean);
249             }
250            //  ExceptionHandlerMethodResolver 是一个工具类,它能够分析一个类,
251            // 获取其中的注解 @ExceptionHandler 注解信息,建立 异常 和 异常处理控制器方法的 映射表
252             ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
253             if (resolver.hasExceptionMappings()) {
254               //  resolver 分析完 beanType,发现其中存在使用@ExceptionHandler注解的方法,
255               // 现在将其记录到 this.exceptionHandlerAdviceCache
256                 this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
257             }
258             
259             if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
260               // 当前 注解 @ControllerAdvice 的 bean 组件实现了接口 ResponseBodyAdvice ,
261               // 现在将其记录到 this.responseBodyAdvice
262                 this.responseBodyAdvice.add(adviceBean);
263             }
264         }
265 
266         if (logger.isDebugEnabled()) {
267             int handlerSize = this.exceptionHandlerAdviceCache.size();
268             int adviceSize = this.responseBodyAdvice.size();
269             if (handlerSize == 0 && adviceSize == 0) {
270                 logger.debug("ControllerAdvice beans: none");
271             }
272             else {
273                 logger.debug("ControllerAdvice beans: " +
274                         handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
275             }
276         }
277     }
278 
279     /**
280      * Return an unmodifiable Map with the {@link ControllerAdvice @ControllerAdvice}
281      * beans discovered in the ApplicationContext. The returned map will be empty if
282      * the method is invoked before the bean has been initialized via
283      * {@link #afterPropertiesSet()}.
284      */
285     public Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> 
286         getExceptionHandlerAdviceCache() {
287         return Collections.unmodifiableMap(this.exceptionHandlerAdviceCache);
288     }
289 
290     /**
291      * 这里返回一个 HandlerMethodArgumentResolver 列表,是缺省情况下使用的异常处理
292      * 控制器方法参数解析器
293      * Return the list of argument resolvers to use including built-in resolvers
294      * and custom resolvers provided via {@link #setCustomArgumentResolvers}.
295      */
296     protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
297         List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
298 
299         // Annotation-based argument resolution
300        // 内置,基于注解的控制器方法参数解析 
301         resolvers.add(new SessionAttributeMethodArgumentResolver());
302         resolvers.add(new RequestAttributeMethodArgumentResolver());
303 
304         // Type-based argument resolution
305        // 内置,基于类型的控制器方法参数解析
306         resolvers.add(new ServletRequestMethodArgumentResolver());
307         resolvers.add(new ServletResponseMethodArgumentResolver());
308         resolvers.add(new RedirectAttributesMethodArgumentResolver());
309         resolvers.add(new ModelMethodProcessor());
310 
311         // Custom arguments
312        // 扩展,可以由开发人员定制 
313         if (getCustomArgumentResolvers() != null) {
314             resolvers.addAll(getCustomArgumentResolvers());
315         }
316 
317         return resolvers;
318     }
319 
320     /**
321      *  返回一个 HandlerMethodReturnValueHandler 列表
322      * 用来处理异常处理控制器方法的返回值
323      * Return the list of return value handlers to use including built-in and
324      * custom handlers provided via {@link #setReturnValueHandlers}.
325      */
326     protected List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
327         List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();
328 
329         // Single-purpose return value types
330         handlers.add(new ModelAndViewMethodReturnValueHandler());
331         handlers.add(new ModelMethodProcessor());
332         handlers.add(new ViewMethodReturnValueHandler());
333         handlers.add(new HttpEntityMethodProcessor(
334                 getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
335 
336         // Annotation-based return value types
337         handlers.add(new ModelAttributeMethodProcessor(false));
338         handlers.add(new RequestResponseBodyMethodProcessor(
339                 getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
340 
341         // Multi-purpose return value types
342         handlers.add(new ViewNameMethodReturnValueHandler());
343         handlers.add(new MapMethodProcessor());
344 
345         // Custom return value types
346         if (getCustomReturnValueHandlers() != null) {
347             handlers.addAll(getCustomReturnValueHandlers());
348         }
349 
350         // Catch-all
351        // 兜底的返回值解析器 
352         handlers.add(new ModelAttributeMethodProcessor(true));
353 
354         return handlers;
355     }
356 
357 
358     /**
359      * Find an {@code @ExceptionHandler} method and invoke it to handle the raised exception.
360      */
361     @Override
362     @Nullable
363     protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
364             HttpServletResponse response, @Nullable HandlerMethod handlerMethod, 
365             Exception exception) {
366 
367        // 考虑发生异常 exception 的控制器方法的所在类,以及所有的 @ControllerAdvice 类中的
368        // 所有 @ExceptionHandler 异常处理控制器方法,看看有没有能处理该异常的异常处理控制器方法,
369        // 如果有则继续,如果没有,则返回 null,调用者会考虑其他方案
370         ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(
371                 handlerMethod, exception);
372         if (exceptionHandlerMethod == null) {
373             return null;
374         }
375 
376        // 现在已经找到能处理异常 exception 的异常处理控制器方法了,包装保存在 
377        // exceptionHandlerMethod 中, 现在准备调用它 :
378        // 1. 设置调用所需要的工具 : 参数解析器,返回值处理器
379        // 2. 准备目标异常处理控制器方法执行过程中需要保存指定决定的容器对象 mavContainer
380        // 3. 调用目标异常处理控制器方法
381         if (this.argumentResolvers != null) {
382             exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
383         }
384         if (this.returnValueHandlers != null) {
385             exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
386         }
387 
388         ServletWebRequest webRequest = new ServletWebRequest(request, response);
389         ModelAndViewContainer mavContainer = new ModelAndViewContainer();
390 
391        // 调用目标异常处理控制器方法并做异常处理 
392         try {
393             if (logger.isDebugEnabled()) {
394                 logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
395             }
396             Throwable cause = exception.getCause();
397             if (cause != null) {
398                 // Expose cause as provided argument as well
399                 exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, 
400                     exception, cause, handlerMethod);
401             }
402             else {
403                 // Otherwise, just the given exception as-is
404                 exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, 
405                         handlerMethod);
406             }
407         }
408         catch (Throwable invocationEx) {
409             // Any other than the original exception is unintended here,
410             // probably an accident (e.g. failed assertion or the like).
411             if (invocationEx != exception && logger.isWarnEnabled()) {
412                 logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
413             }
414             // Continue with default processing of the original exception...
415           // 异常处理控制器方法执行遇到异常,这里返回null,让调用者继续使用缺省方式处理
416           // 原来需要处理的异常,注意,指的是参数 exception,而不是这里抓到的异常
417           // invocationEx
418             return null;
419         }
420 
421         
422         if (mavContainer.isRequestHandled()) {
423            // 异常处理控制器方法将请求处理完全了,这里返回空  ModelAndView() 对象,
424            // 告诉调用者不需要继续解析和渲染视图了
425             return new ModelAndView();
426         }
427         else {
428           //  异常处理控制器方法正常执行结果,但没有将请求处理完全,现在构造 ModelAndView,
429           // 调用者需要继续解析和渲染视图
430             ModelMap model = mavContainer.getModel();
431             HttpStatus status = mavContainer.getStatus();
432             ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
433             mav.setViewName(mavContainer.getViewName());
434             if (!mavContainer.isViewReference()) {
435                 mav.setView((View) mavContainer.getView());
436             }
437             if (model instanceof RedirectAttributes) {
438                 Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
439                 RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
440             }
441             return mav;
442         }
443     }
444 
445     /**
446      * Find an {@code @ExceptionHandler} method for the given exception. The default
447      * implementation searches methods in the class hierarchy of the controller first
448      * and if not found, it continues searching for additional {@code @ExceptionHandler}
449      * methods assuming some {@linkplain ControllerAdvice @ControllerAdvice}
450      * Spring-managed beans were detected.
451      * @param handlerMethod the method where the exception was raised (may be {@code null})
452      * @param exception the raised exception
453      * @return a method to handle the exception, or {@code null} if none
454      */
455     @Nullable
456     protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
457             @Nullable HandlerMethod handlerMethod, Exception exception) {
458 
459         Class<?> handlerType = null;
460 
461         if (handlerMethod != null) {
462             // Local exception handler methods on the controller class itself.
463             // To be invoked through the proxy, even in case of an interface-based proxy.
464           // 首先尝试分析控制器方法所在类类自身是否有使用注解 @ExceptionHander,
465           // 这一分析动作跟分析一个@ControllerAdvice组件类是一样的,也是使用
466           // ExceptionHandlerMethodResolver
467             handlerType = handlerMethod.getBeanType();
468             ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
469             if (resolver == null) {
470                 resolver = new ExceptionHandlerMethodResolver(handlerType);
471                 this.exceptionHandlerCache.put(handlerType, resolver);
472             }
473             
474            // 现在看控制方法所在类是否有能处理指定异常 exception 的异常处理控制器方法,
475            // 有的话使用该方法构建 ServletInvocableHandlerMethod, 用于处理该异常
476             Method method = resolver.resolveMethod(exception);
477             if (method != null) {
478                 return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
479             }
480             // For advice applicability check below (involving base packages, assignable types
481             // and annotation presence), use target class instead of interface-based proxy.
482             if (Proxy.isProxyClass(handlerType)) {
483                 handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
484             }
485         }
486 
487        // 如果控制器方法所在类不存在能处理指定异常 exception 的异常处理控制器方法,
488        // 现在查看所有的 @ControllerAdvice 类中是否有能处理指定异常 exception
489        // 的异常处理控制器方法,找到的话相应地构造 ServletInvocableHandlerMethod 
490        // 并返回,用于处理该异常
491         for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry 
492                 : this.exceptionHandlerAdviceCache.entrySet()) {
493             ControllerAdviceBean advice = entry.getKey();
494             if (advice.isApplicableToBeanType(handlerType)) {
495                 ExceptionHandlerMethodResolver resolver = entry.getValue();
496                 Method method = resolver.resolveMethod(exception);
497                 if (method != null) {
498                     return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
499                 }
500             }
501         }
502 
503        // 从发生异常 exception 的控制器方法所在类和所有的  @ControllerAdvice 类中
504        // 都找不到合适的异常处理控制器方法,现在返回 null,意思是告知调用者:我处理
505        // 不了这一个异常,你再看看其他 HandlerExceptionResolver ?
506         return null;
507     }
508 
509 }
View Code

org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver

如果异常是ResponseStatusException或者使用了注解@ResponseStatus,则根据异常解析出status和reason、调用response.sendError方法、返回空ModelAndView;若所发生异常的cause也是一个异常,则递归执行上述流程。源码:

  1 package org.springframework.web.servlet.mvc.annotation;
  2 
  3 // 省略 import 行
  4 
  5 
  6 public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver 
  7         implements MessageSourceAware {
  8 
  9     @Nullable
 10     private MessageSource messageSource;
 11 
 12 
 13     // MessageSourceAware 接口定义的方法,
 14     // 会在当前 bean 创建的合适时机被容器设置该属性
 15     @Override
 16     public void setMessageSource(MessageSource messageSource) {
 17         this.messageSource = messageSource;
 18     }
 19 
 20 
 21     @Override
 22     @Nullable
 23     protected ModelAndView doResolveException(
 24             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
 25 
 26         try {
 27           //第1种情况处理 : 异常自身就是 ResponseStatusException 异常的情况 
 28             if (ex instanceof ResponseStatusException) {
 29                 return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
 30             }
 31 
 32           //第2种情况处理 : 尝试从异常上获取注解 @ResponseStatus 信息  
 33             ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(
 34                 ex.getClass(), ResponseStatus.class);
 35             if (status != null) {
 36                 return resolveResponseStatus(status, request, response, handler, ex);
 37             }
 38 
 39           //第3种情况处理 :  异常的 cause 还是异常,递归调用该方法进行处理
 40             if (ex.getCause() instanceof Exception) {
 41                 return doResolveException(request, response, handler, (Exception) ex.getCause());
 42             }
 43         }
 44         catch (Exception resolveEx) {
 45             if (logger.isWarnEnabled()) {
 46                 logger.warn("Failure while trying to resolve exception [" 
 47                     + ex.getClass().getName() + "]", resolveEx);
 48             }
 49         }
 50         
 51        // 通过null告诉调用方没能处理的了该异常
 52         return null;
 53     }
 54 
 55     /**
 56      * Template method that handles the {@link ResponseStatus @ResponseStatus} annotation.
 57      * <p>The default implementation delegates to {@link #applyStatusAndReason}
 58      * with the status code and reason from the annotation.
 59      * @param responseStatus the {@code @ResponseStatus} annotation
 60      * @param request current HTTP request
 61      * @param response current HTTP response
 62      * @param handler the executed handler, or {@code null} if none chosen at the
 63      * time of the exception, e.g. if multipart resolution failed
 64      * @param ex the exception
 65      * @return an empty ModelAndView, i.e. exception resolved
 66      */
 67     protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,
 68             HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
 69 
 70         int statusCode = responseStatus.code().value();
 71         String reason = responseStatus.reason();
 72         return applyStatusAndReason(statusCode, reason, response);
 73     }
 74 
 75     /**
 76      * Template method that handles an {@link ResponseStatusException}.
 77      * <p>The default implementation delegates to {@link #applyStatusAndReason}
 78      * with the status code and reason from the exception.
 79      * @param ex the exception
 80      * @param request current HTTP request
 81      * @param response current HTTP response
 82      * @param handler the executed handler, or {@code null} if none chosen at the
 83      * time of the exception, e.g. if multipart resolution failed
 84      * @return an empty ModelAndView, i.e. exception resolved
 85      * @since 5.0
 86      */
 87     protected ModelAndView resolveResponseStatusException(ResponseStatusException ex,
 88             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) 
 89             throws Exception {
 90 
 91         int statusCode = ex.getStatus().value();
 92         String reason = ex.getReason();
 93         return applyStatusAndReason(statusCode, reason, response);
 94     }
 95 
 96     /**
 97      * Apply the resolved status code and reason to the response.
 98      * <p>The default implementation sends a response error using
 99      * {@link HttpServletResponse#sendError(int)} or
100      * {@link HttpServletResponse#sendError(int, String)} if there is a reason
101      * and then returns an empty ModelAndView.
102      * @param statusCode the HTTP status code
103      * @param reason the associated reason (may be {@code null} or empty)
104      * @param response current HTTP response
105      * @since 5.0
106      */
107     protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, 
108             HttpServletResponse response)
109             throws IOException {
110 
111         if (!StringUtils.hasLength(reason)) {
112             // reason 为空的情况 
113             response.sendError(statusCode);
114         }
115         else {
116             // reason 不为空的情况
117              // 如果也设置了消息源 this.messageSource, 尝试从其中解析消息         
118             String resolvedReason = (this.messageSource != null ?
119                     this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :
120                     reason);
121             response.sendError(statusCode, resolvedReason);
122         }
123         
124         // 返回一个空 ModelAndView 对象,告知调用方请求已经被处理完,使用缺省视图解析和渲染机制 
125         return new ModelAndView();
126     }
127 
128 }
View Code

org.springframework.web.servlet.handler.SimpleMappingExceptionResolver

记录异常与视图名间的映射关系,以及异常与http status的映射关系。处理时根据异常及此映射关系查找ModelAndView及http status来处理异常,若未找到则使用设置的缺省ModelAndView和http status,若缺省者也未设置则返回null(相当于不处理)。源码:

  1 package org.springframework.web.servlet.handler;
  2 
  3 // 省略 import 行
  4 
  5 
  6 public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionResolver {
  7 
  8     /** The default name of the exception attribute: "exception". */
  9     public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
 10 
 11 
 12     // 保存异常类名称和异常处理视图名称的映射
 13     @Nullable
 14     private Properties exceptionMappings;
 15 
 16     @Nullable
 17     private Class<?>[] excludedExceptions;
 18 
 19     // 缺省用于异常处理的错误视图名称
 20     // 缺省值为 null
 21     // 如果设置该值,某个异常被当前 SimpleMappingExceptionResolver 解析时,
 22     // 会首先通过 exceptionMappings 查看对应的 异常处理视图,如果没找到,
 23     // 则会用该值渲染视图。所以如果设置了该值,可以理解为,SimpleMappingExceptionResolver
 24     // 接管了对所有异常的处理
 25     @Nullable
 26     private String defaultErrorView;
 27 
 28     // 缺省状态码
 29     @Nullable
 30     private Integer defaultStatusCode;
 31 
 32     // 保存错误视图名称/HTTP状态之间的映射
 33     private Map<String, Integer> statusCodes = new HashMap<>();
 34 
 35     @Nullable
 36     private String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE;
 37 
 38 
 39     /**
 40      * Set the mappings between exception class names and error view names.
 41      * 设置异常类名称和错误视图名称之间的映射。
 42      * 这里异常类名称可以是完整类名称的一部分,而不必是全部。目前还不支持通配符。
 43      * The exception class name can be a substring, with no wildcard support at present.
 44      * A value of "ServletException" would match {@code javax.servlet.ServletException}
 45      * and subclasses, for example.
 46      * <p><b>NB:</b> Consider carefully how
 47      * specific the pattern is, and whether to include package information (which isn't mandatory).
 48      * For example, "Exception" will match nearly anything, and will probably hide other rules.
 49      * "java.lang.Exception" would be correct if "Exception" was meant to define a rule for all
 50      * checked exceptions. With more unusual exception names such as "BaseBusinessException"
 51      * there's no need to use a FQN.
 52      * @param mappings exception patterns (can also be fully qualified class names) as keys,
 53      * and error view names as values
 54      */
 55     public void setExceptionMappings(Properties mappings) {
 56         this.exceptionMappings = mappings;
 57     }
 58 
 59     /**
 60      * Set one or more exceptions to be excluded from the exception mappings.
 61      * Excluded exceptions are checked first and if one of them equals the actual
 62      * exception, the exception will remain unresolved.
 63      * @param excludedExceptions one or more excluded exception types
 64      */
 65     public void setExcludedExceptions(Class<?>... excludedExceptions) {
 66         this.excludedExceptions = excludedExceptions;
 67     }
 68 
 69     /**
 70      * Set the name of the default error view.
 71      * This view will be returned if no specific mapping was found.
 72      * <p>Default is none.
 73      */
 74     public void setDefaultErrorView(String defaultErrorView) {
 75         this.defaultErrorView = defaultErrorView;
 76     }
 77 
 78     /**
 79      * Set the HTTP status code that this exception resolver will apply for a given
 80      * resolved error view. Keys are view names; values are status codes.
 81      * <p>Note that this error code will only get applied in case of a top-level request.
 82      * It will not be set for an include request, since the HTTP status cannot be modified
 83      * from within an include.
 84      * <p>If not specified, the default status code will be applied.
 85      * @see #setDefaultStatusCode(int)
 86      */
 87     public void setStatusCodes(Properties statusCodes) {
 88         for (Enumeration<?> enumeration = statusCodes.propertyNames(); enumeration.hasMoreElements();) {
 89             String viewName = (String) enumeration.nextElement();
 90             Integer statusCode = Integer.valueOf(statusCodes.getProperty(viewName));
 91             this.statusCodes.put(viewName, statusCode);
 92         }
 93     }
 94 
 95     /**
 96      * An alternative to {@link #setStatusCodes(Properties)} for use with
 97      * Java-based configuration.
 98      */
 99     public void addStatusCode(String viewName, int statusCode) {
100         this.statusCodes.put(viewName, statusCode);
101     }
102 
103     /**
104      * Returns the HTTP status codes provided via {@link #setStatusCodes(Properties)}.
105      * Keys are view names; values are status codes.
106      */
107     public Map<String, Integer> getStatusCodesAsMap() {
108         return Collections.unmodifiableMap(this.statusCodes);
109     }
110 
111     /**
112      * Set the default HTTP status code that this exception resolver will apply
113      * if it resolves an error view and if there is no status code mapping defined.
114      * <p>Note that this error code will only get applied in case of a top-level request.
115      * It will not be set for an include request, since the HTTP status cannot be modified
116      * from within an include.
117      * <p>If not specified, no status code will be applied, either leaving this to the
118      * controller or view, or keeping the servlet engine's default of 200 (OK).
119      * @param defaultStatusCode the HTTP status code value, for example 500
120      * ({@link HttpServletResponse#SC_INTERNAL_SERVER_ERROR}) or 404 ({@link HttpServletResponse#SC_NOT_FOUND})
121      * @see #setStatusCodes(Properties)
122      */
123     public void setDefaultStatusCode(int defaultStatusCode) {
124         this.defaultStatusCode = defaultStatusCode;
125     }
126 
127     /**
128      * Set the name of the model attribute as which the exception should be exposed.
129      * Default is "exception".
130      * <p>This can be either set to a different attribute name or to {@code null}
131      * for not exposing an exception attribute at all.
132      * @see #DEFAULT_EXCEPTION_ATTRIBUTE
133      */
134     public void setExceptionAttribute(@Nullable String exceptionAttribute) {
135         this.exceptionAttribute = exceptionAttribute;
136     }
137 
138 
139     /**
140      * Actually resolve the given exception that got thrown during on handler execution,
141      * returning a ModelAndView that represents a specific error page if appropriate.
142      * <p>May be overridden in subclasses, in order to apply specific exception checks.
143      * Note that this template method will be invoked <i>after</i> checking whether this
144      * resolved applies ("mappedHandlers" etc), so an implementation may simply proceed
145      * with its actual exception handling.
146      * @param request current HTTP request
147      * @param response current HTTP response
148      * @param handler the executed handler, or {@code null} if none chosen at the time
149      * of the exception (for example, if multipart resolution failed)
150      * @param ex the exception that got thrown during handler execution
151      * @return a corresponding {@code ModelAndView} to forward to,
152      * or {@code null} for default processing in the resolution chain
153      */
154     @Override
155     @Nullable
156     protected ModelAndView doResolveException(
157             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
158 
159         // Expose ModelAndView for chosen error view.
160        // 尝试根据异常类型查找 exceptionMappings 是否存在对应的错误视图名称,有的话记录到 viewName 
161         String viewName = determineViewName(ex, request);
162         if (viewName != null) {
163             // Apply HTTP status code for error views, if specified.
164             // Only apply it if we're processing a top-level request.
165           // 结合 错误视图名称/HTTP状态码映射表 和 缺省状态码属性 中查看是否存在对应 
166           // viewName 的 HTTP状态码,有的话设置到响应对象上
167             Integer statusCode = determineStatusCode(request, viewName);
168             if (statusCode != null) {
169                 applyStatusCodeIfPossible(request, response, statusCode);
170             }
171           
172           // 使用找到的视图名称构建相应的 ModelAndView 对象并返回给调用者
173           // 用以渲染错误视图给请求者
174             return getModelAndView(viewName, ex, request);
175         }
176         else {
177           // 返回 null 给调用者表明自己没能处理该异常 
178             return null;
179         }
180     }
181 
182     /**
183      * Determine the view name for the given exception, first checking against the
184      * {@link #setExcludedExceptions(Class[]) "excludedExecptions"}, then searching the
185      * {@link #setExceptionMappings "exceptionMappings"}, and finally using the
186      * {@link #setDefaultErrorView "defaultErrorView"} as a fallback.
187      * @param ex the exception that got thrown during handler execution
188      * @param request current HTTP request (useful for obtaining metadata)
189      * @return the resolved view name, or {@code null} if excluded or none found
190      */
191     @Nullable
192     protected String determineViewName(Exception ex, HttpServletRequest request) {
193         String viewName = null;
194         if (this.excludedExceptions != null) {
195             for (Class<?> excludedEx : this.excludedExceptions) {
196                 if (excludedEx.equals(ex.getClass())) {
197                     return null;
198                 }
199             }
200         }
201         // Check for specific exception mappings.
202         if (this.exceptionMappings != null) {
203             viewName = findMatchingViewName(this.exceptionMappings, ex);
204         }
205         // Return default error view else, if defined.
206         if (viewName == null && this.defaultErrorView != null) {
207             if (logger.isDebugEnabled()) {
208                 logger.debug("Resolving to default view '" + this.defaultErrorView + "'");
209             }
210             viewName = this.defaultErrorView;
211         }
212         return viewName;
213     }
214 
215     /**
216      * Find a matching view name in the given exception mappings.
217      * @param exceptionMappings mappings between exception class names and error view names
218      * @param ex the exception that got thrown during handler execution
219      * @return the view name, or {@code null} if none found
220      * @see #setExceptionMappings
221      */
222     @Nullable
223     protected String findMatchingViewName(Properties exceptionMappings, Exception ex) {
224         String viewName = null;
225         String dominantMapping = null;
226         int deepest = Integer.MAX_VALUE;
227         for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements();) {
228             String exceptionMapping = (String) names.nextElement();
229             int depth = getDepth(exceptionMapping, ex);
230             if (depth >= 0 && (depth < deepest || (depth == deepest &&
231                     dominantMapping != null && exceptionMapping.length() > dominantMapping.length()))) {
232                 deepest = depth;
233                 dominantMapping = exceptionMapping;
234                 viewName = exceptionMappings.getProperty(exceptionMapping);
235             }
236         }
237         if (viewName != null && logger.isDebugEnabled()) {
238             logger.debug("Resolving to view '" + viewName + "' based on mapping [" + dominantMapping + "]");
239         }
240         return viewName;
241     }
242 
243     /**
244      * Return the depth to the superclass matching.
245      * <p>0 means ex matches exactly. Returns -1 if there's no match.
246      * Otherwise, returns depth. Lowest depth wins.
247      */
248     protected int getDepth(String exceptionMapping, Exception ex) {
249         return getDepth(exceptionMapping, ex.getClass(), 0);
250     }
251 
252     private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) {
253         if (exceptionClass.getName().contains(exceptionMapping)) {
254             // Found it!
255             return depth;
256         }
257         // If we've gone as far as we can go and haven't found it...
258         if (exceptionClass == Throwable.class) {
259             return -1;
260         }
261         return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
262     }
263 
264     /**
265      * Determine the HTTP status code to apply for the given error view.
266      * <p>The default implementation returns the status code for the given view name (specified through the
267      * {@link #setStatusCodes(Properties) statusCodes} property), or falls back to the
268      * {@link #setDefaultStatusCode defaultStatusCode} if there is no match.
269      * <p>Override this in a custom subclass to customize this behavior.
270      * @param request current HTTP request
271      * @param viewName the name of the error view
272      * @return the HTTP status code to use, or {@code null} for the servlet container's default
273      * (200 in case of a standard error view)
274      * @see #setDefaultStatusCode
275      * @see #applyStatusCodeIfPossible
276      */
277     @Nullable
278     protected Integer determineStatusCode(HttpServletRequest request, String viewName) {
279         if (this.statusCodes.containsKey(viewName)) {
280             return this.statusCodes.get(viewName);
281         }
282         return this.defaultStatusCode;
283     }
284 
285     /**
286      * Apply the specified HTTP status code to the given response, if possible (that is,
287      * if not executing within an include request).
288      * @param request current HTTP request
289      * @param response current HTTP response
290      * @param statusCode the status code to apply
291      * @see #determineStatusCode
292      * @see #setDefaultStatusCode
293      * @see HttpServletResponse#setStatus
294      */
295     protected void applyStatusCodeIfPossible(HttpServletRequest request, 
296                         HttpServletResponse response, int statusCode) {
297         if (!WebUtils.isIncludeRequest(request)) {
298             if (logger.isDebugEnabled()) {
299                 logger.debug("Applying HTTP status " + statusCode);
300             }
301             response.setStatus(statusCode);
302             request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, statusCode);
303         }
304     }
305 
306     /**
307      * Return a ModelAndView for the given request, view name and exception.
308      * <p>The default implementation delegates to {@link #getModelAndView(String, Exception)}.
309      * @param viewName the name of the error view
310      * @param ex the exception that got thrown during handler execution
311      * @param request current HTTP request (useful for obtaining metadata)
312      * @return the ModelAndView instance
313      */
314     protected ModelAndView getModelAndView(String viewName, Exception ex, HttpServletRequest request) {
315         return getModelAndView(viewName, ex);
316     }
317 
318     /**
319      * Return a ModelAndView for the given view name and exception.
320      * <p>The default implementation adds the specified exception attribute.
321      * Can be overridden in subclasses.
322      * @param viewName the name of the error view
323      * @param ex the exception that got thrown during handler execution
324      * @return the ModelAndView instance
325      * @see #setExceptionAttribute
326      */
327     protected ModelAndView getModelAndView(String viewName, Exception ex) {
328         ModelAndView mv = new ModelAndView(viewName);
329         if (this.exceptionAttribute != null) {
330             mv.addObject(this.exceptionAttribute, ex);
331         }
332         return mv;
333     }
334 
335 }
View Code

org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

Spring MVC默认的异常处理器,解析时将标准MVC异常翻译成HTTP状态码、调用响应对象的方法#sendError、返回一个空ModelAndView。源码:

  1 package org.springframework.web.servlet.mvc.support;
  2 
  3 
  4 public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
  5 
  6     /**
  7      * Log category to use when no mapped handler is found for a request.
  8      * @see #pageNotFoundLogger
  9      */
 10     public static final String PAGE_NOT_FOUND_LOG_CATEGORY = 
 11         "org.springframework.web.servlet.PageNotFound";
 12 
 13     /**
 14      * Additional logger to use when no mapped handler is found for a request.
 15      * @see #PAGE_NOT_FOUND_LOG_CATEGORY
 16      */
 17     protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
 18 
 19 
 20     /**
 21      * Sets the #setOrder(int) order to  #LOWEST_PRECEDENCE.
 22      */
 23     public DefaultHandlerExceptionResolver() {
 24         // 注意该构造函数中将该异常解析器的优先级设置为最低
 25         setOrder(Ordered.LOWEST_PRECEDENCE);
 26         setWarnLogCategory(getClass().getName());
 27     }
 28 
 29 
 30     // 根据参数 ex 异常对象的类型解析参数,将其转换成一个 ModelAndView 对象返回,
 31     // 并且注意该过程中涉及到对 response 头部的设置,已经对其调用  sendError() .
 32     // 如果参数 ex 不能被当前异常处理器处理,返回 null
 33     // 该方法对基类定义的该方法提供了实现,该方法会被基类公开方法 resolveException() 使用,
 34     // resolveException() 是接口 HandlerExceptionResolver 所定义的方法
 35     @Override
 36     @Nullable
 37     protected ModelAndView doResolveException(
 38             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, 
 39             Exception ex) {
 40 
 41         try {
 42             if (ex instanceof HttpRequestMethodNotSupportedException) {
 43                 return handleHttpRequestMethodNotSupported(
 44                         (HttpRequestMethodNotSupportedException) ex, request, response, handler);
 45             }
 46             else if (ex instanceof HttpMediaTypeNotSupportedException) {
 47                 return handleHttpMediaTypeNotSupported(
 48                         (HttpMediaTypeNotSupportedException) ex, request, response, handler);
 49             }
 50             else if (ex instanceof HttpMediaTypeNotAcceptableException) {
 51                 return handleHttpMediaTypeNotAcceptable(
 52                         (HttpMediaTypeNotAcceptableException) ex, request, response, handler);
 53             }
 54             else if (ex instanceof MissingPathVariableException) {
 55                 return handleMissingPathVariable(
 56                         (MissingPathVariableException) ex, request, response, handler);
 57             }
 58             else if (ex instanceof MissingServletRequestParameterException) {
 59                 return handleMissingServletRequestParameter(
 60                         (MissingServletRequestParameterException) ex, request, response, handler);
 61             }
 62             else if (ex instanceof ServletRequestBindingException) {
 63                 return handleServletRequestBindingException(
 64                         (ServletRequestBindingException) ex, request, response, handler);
 65             }
 66             else if (ex instanceof ConversionNotSupportedException) {
 67                 return handleConversionNotSupported(
 68                         (ConversionNotSupportedException) ex, request, response, handler);
 69             }
 70             else if (ex instanceof TypeMismatchException) {
 71                 return handleTypeMismatch(
 72                         (TypeMismatchException) ex, request, response, handler);
 73             }
 74             else if (ex instanceof HttpMessageNotReadableException) {
 75                 return handleHttpMessageNotReadable(
 76                         (HttpMessageNotReadableException) ex, request, response, handler);
 77             }
 78             else if (ex instanceof HttpMessageNotWritableException) {
 79                 return handleHttpMessageNotWritable(
 80                         (HttpMessageNotWritableException) ex, request, response, handler);
 81             }
 82             else if (ex instanceof MethodArgumentNotValidException) {
 83                 return handleMethodArgumentNotValidException(
 84                         (MethodArgumentNotValidException) ex, request, response, handler);
 85             }
 86             else if (ex instanceof MissingServletRequestPartException) {
 87                 return handleMissingServletRequestPartException(
 88                         (MissingServletRequestPartException) ex, request, response, handler);
 89             }
 90             else if (ex instanceof BindException) {
 91                 return handleBindException((BindException) ex, request, response, handler);
 92             }
 93             else if (ex instanceof NoHandlerFoundException) {
 94                 return handleNoHandlerFoundException(
 95                         (NoHandlerFoundException) ex, request, response, handler);
 96             }
 97             else if (ex instanceof AsyncRequestTimeoutException) {
 98                 return handleAsyncRequestTimeoutException(
 99                         (AsyncRequestTimeoutException) ex, request, response, handler);
100             }
101         }
102         catch (Exception handlerEx) {
103             if (logger.isWarnEnabled()) {
104                 logger.warn("Failure while trying to resolve exception [" 
105                 + ex.getClass().getName() + "]", handlerEx);
106             }
107         }
108         return null;
109     }
110 
111     /**
112      * Handle the case where no request handler method was found for the particular HTTP request 
113      * method.
114      * The default implementation logs a warning, sends an HTTP 405 error, sets the "Allow" header,
115      * and returns an empty ModelAndView. Alternatively, a fallback view could be chosen,
116      * or the HttpRequestMethodNotSupportedException could be rethrown as-is.
117      * @param ex the HttpRequestMethodNotSupportedException to be handled
118      * @param request current HTTP request
119      * @param response current HTTP response
120      * @param handler the executed handler, or null if none chosen
121      * at the time of the exception (for example, if multipart resolution failed)
122      * @return an empty ModelAndView indicating the exception was handled
123      * @throws IOException potentially thrown from HttpServletResponse#sendError
124      */
125     protected ModelAndView handleHttpRequestMethodNotSupported(
126             HttpRequestMethodNotSupportedException ex,
127             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) 
128             throws IOException {
129 
130         String[] supportedMethods = ex.getSupportedMethods();
131         if (supportedMethods != null) {
132             response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
133         }
134         response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
135         return new ModelAndView();
136     }
137 
138     /**
139      * Handle the case where no org.springframework.http.converter.HttpMessageConverter message 
140      * converters were found for the PUT or POSTed content.
141      * The default implementation sends an HTTP 415 error, sets the "Accept" header,
142      * and returns an empty ModelAndView. Alternatively, a fallback view could
143      * be chosen, or the HttpMediaTypeNotSupportedException could be rethrown as-is.
144      * @param ex the HttpMediaTypeNotSupportedException to be handled
145      * @param request current HTTP request
146      * @param response current HTTP response
147      * @param handler the executed handler
148      * @return an empty ModelAndView indicating the exception was handled
149      * @throws IOException potentially thrown from HttpServletResponse#sendError
150      */
151     protected ModelAndView handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex,
152             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) 
153             throws IOException {
154 
155         response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
156         List<MediaType> mediaTypes = ex.getSupportedMediaTypes();
157         if (!CollectionUtils.isEmpty(mediaTypes)) {
158             response.setHeader("Accept", MediaType.toString(mediaTypes));
159         }
160         return new ModelAndView();
161     }
162 
163     /**
164      * Handle the case where no org.springframework.http.converter.HttpMessageConverter message 
165      * converters
166      * were found that were acceptable for the client (expressed via the Accept header.
167      * The default implementation sends an HTTP 406 error and returns an empty ModelAndView.
168      * Alternatively, a fallback view could be chosen, or the HttpMediaTypeNotAcceptableException
169      * could be rethrown as-is.
170      * @param ex the HttpMediaTypeNotAcceptableException to be handled
171      * @param request current HTTP request
172      * @param response current HTTP response
173      * @param handler the executed handler
174      * @return an empty ModelAndView indicating the exception was handled
175      * @throws IOException potentially thrown from HttpServletResponse#sendError
176      */
177     protected ModelAndView handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex,
178             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) 
179             throws IOException {
180 
181         response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE);
182         return new ModelAndView();
183     }
184 
185     /**
186      * Handle the case when a declared path variable does not match any extracted URI variable.
187      * The default implementation sends an HTTP 500 error, and returns an empty ModelAndView.
188      * Alternatively, a fallback view could be chosen, or the MissingPathVariableException
189      * could be rethrown as-is.
190      * @param ex the MissingPathVariableException to be handled
191      * @param request current HTTP request
192      * @param response current HTTP response
193      * @param handler the executed handler
194      * @return an empty ModelAndView indicating the exception was handled
195      * @throws IOException potentially thrown from HttpServletResponse#sendError
196      * @since 4.2
197      */
198     protected ModelAndView handleMissingPathVariable(MissingPathVariableException ex,
199             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) 
200             throws IOException {
201 
202         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage());
203         return new ModelAndView();
204     }
205 
206     /**
207      * Handle the case when a required parameter is missing.
208      * The default implementation sends an HTTP 400 error, and returns an empty ModelAndView.
209      * Alternatively, a fallback view could be chosen, or the MissingServletRequestParameterException
210      * could be rethrown as-is.
211      * @param ex the MissingServletRequestParameterException to be handled
212      * @param request current HTTP request
213      * @param response current HTTP response
214      * @param handler the executed handler
215      * @return an empty ModelAndView indicating the exception was handled
216      * @throws IOException potentially thrown from HttpServletResponse#sendError
217      */
218     protected ModelAndView handleMissingServletRequestParameter(
219             MissingServletRequestParameterException ex,
220             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) 
221             throws IOException {
222 
223         response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
224         return new ModelAndView();
225     }
226 
227     /**
228      * Handle the case when an unrecoverable binding exception occurs - e.g. required header, 
229      * required cookie.
230      * The default implementation sends an HTTP 400 error, and returns an empty ModelAndView.
231      * Alternatively, a fallback view could be chosen, or the exception could be rethrown as-is.
232      * @param ex the exception to be handled
233      * @param request current HTTP request
234      * @param response current HTTP response
235      * @param handler the executed handler
236      * @return an empty ModelAndView indicating the exception was handled
237      * @throws IOException potentially thrown from HttpServletResponse#sendError
238      */
239     protected ModelAndView handleServletRequestBindingException(ServletRequestBindingException ex,
240             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) 
241             throws IOException {
242 
243         response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
244         return new ModelAndView();
245     }
246 
247     /**
248      * Handle the case when a org.springframework.web.bind.WebDataBinder conversion cannot occur.
249      * The default implementation sends an HTTP 500 error, and returns an empty ModelAndView.
250      * Alternatively, a fallback view could be chosen, or the ConversionNotSupportedException could be
251      * rethrown as-is.
252      * @param ex the ConversionNotSupportedException to be handled
253      * @param request current HTTP request
254      * @param response current HTTP response
255      * @param handler the executed handler
256      * @return an empty ModelAndView indicating the exception was handled
257      * @throws IOException potentially thrown from HttpServletResponse#sendError
258      */
259     protected ModelAndView handleConversionNotSupported(ConversionNotSupportedException ex,
260             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) 
261             throws IOException {
262 
263         sendServerError(ex, request, response);
264         return new ModelAndView();
265     }
266 
267     /**
268      * Handle the case when a org.springframework.web.bind.WebDataBinder conversion error occurs.
269      * The default implementation sends an HTTP 400 error, and returns an empty ModelAndView.
270      * Alternatively, a fallback view could be chosen, or the TypeMismatchException could be 
271      * rethrown as-is.
272      * @param ex the TypeMismatchException to be handled
273      * @param request current HTTP request
274      * @param response current HTTP response
275      * @param handler the executed handler
276      * @return an empty ModelAndView indicating the exception was handled
277      * @throws IOException potentially thrown from HttpServletResponse#sendError
278      */
279     protected ModelAndView handleTypeMismatch(TypeMismatchException ex,
280             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) 
281             throws IOException {
282 
283         response.sendError(HttpServletResponse.SC_BAD_REQUEST);
284         return new ModelAndView();
285     }
286 
287     /**
288      * Handle the case where a org.springframework.http.converter.HttpMessageConverter message 
289      * converter cannot read from a HTTP request.
290      * The default implementation sends an HTTP 400 error, and returns an empty ModelAndView.
291      * Alternatively, a fallback view could be chosen, or the HttpMessageNotReadableException could be
292      * rethrown as-is.
293      * @param ex the HttpMessageNotReadableException to be handled
294      * @param request current HTTP request
295      * @param response current HTTP response
296      * @param handler the executed handler
297      * @return an empty ModelAndView indicating the exception was handled
298      * @throws IOException potentially thrown from HttpServletResponse#sendError
299      */
300     protected ModelAndView handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
301             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) 
302             throws IOException {
303 
304         response.sendError(HttpServletResponse.SC_BAD_REQUEST);
305         return new ModelAndView();
306     }
307 
308     /**
309      * Handle the case where a
310      * org.springframework.http.converter.HttpMessageConverter message converter
311      * cannot write to a HTTP request.
312      * The default implementation sends an HTTP 500 error, and returns an empty ModelAndView.
313      * Alternatively, a fallback view could be chosen, or the HttpMessageNotWritableException could
314      * be rethrown as-is.
315      * @param ex the HttpMessageNotWritableException to be handled
316      * @param request current HTTP request
317      * @param response current HTTP response
318      * @param handler the executed handler
319      * @return an empty ModelAndView indicating the exception was handled
320      * @throws IOException potentially thrown from HttpServletResponse#sendError
321      */
322     protected ModelAndView handleHttpMessageNotWritable(HttpMessageNotWritableException ex,
323             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) 
324             throws IOException {
325 
326         sendServerError(ex, request, response);
327         return new ModelAndView();
328     }
329 
330     /**
331      * Handle the case where an argument annotated with @Valid such as
332      * an RequestBody or RequestPart argument fails validation.
333      * By default, an HTTP 400 error is sent back to the client.
334      * @param request current HTTP request
335      * @param response current HTTP response
336      * @param handler the executed handler
337      * @return an empty ModelAndView indicating the exception was handled
338      * @throws IOException potentially thrown from HttpServletResponse#sendError
339      */
340     protected ModelAndView handleMethodArgumentNotValidException(MethodArgumentNotValidException ex,
341             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) 
342             throws IOException {
343 
344         response.sendError(HttpServletResponse.SC_BAD_REQUEST);
345         return new ModelAndView();
346     }
347 
348     /**
349      * Handle the case where an RequestPart @RequestPart, a MultipartFile,
350      * or a javax.servlet.http.Part argument is required but is missing.
351      * By default, an HTTP 400 error is sent back to the client.
352      * @param request current HTTP request
353      * @param response current HTTP response
354      * @param handler the executed handler
355      * @return an empty ModelAndView indicating the exception was handled
356      * @throws IOException potentially thrown from HttpServletResponse#sendError
357      */
358     protected ModelAndView handleMissingServletRequestPartException(
359             MissingServletRequestPartException ex,
360             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) 
361             throws IOException {
362 
363         response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
364         return new ModelAndView();
365     }
366 
367     /**
368      * Handle the case where an ModelAttribute @ModelAttribute method
369      * argument has binding or validation errors and is not followed by another
370      * method argument of type BindingResult.
371      * By default, an HTTP 400 error is sent back to the client.
372      * @param request current HTTP request
373      * @param response current HTTP response
374      * @param handler the executed handler
375      * @return an empty ModelAndView indicating the exception was handled
376      * @throws IOException potentially thrown from HttpServletResponse#sendError
377      */
378     protected ModelAndView handleBindException(BindException ex, HttpServletRequest request,
379             HttpServletResponse response, @Nullable Object handler) throws IOException {
380 
381         response.sendError(HttpServletResponse.SC_BAD_REQUEST);
382         return new ModelAndView();
383     }
384 
385     /**
386      * Handle the case where no handler was found during the dispatch.
387      * The default implementation sends an HTTP 404 error and returns an empty
388      * ModelAndView. Alternatively, a fallback view could be chosen,
389      * or the NoHandlerFoundException could be rethrown as-is.
390      * @param ex the NoHandlerFoundException to be handled
391      * @param request current HTTP request
392      * @param response current HTTP response
393      * @param handler the executed handler, or null if none chosen
394      * at the time of the exception (for example, if multipart resolution failed)
395      * @return an empty ModelAndView indicating the exception was handled
396      * @throws IOException potentially thrown from HttpServletResponse#sendError
397      * @since 4.0
398      */
399     protected ModelAndView handleNoHandlerFoundException(NoHandlerFoundException ex,
400             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) 
401             throws IOException {
402 
403         pageNotFoundLogger.warn(ex.getMessage());
404         response.sendError(HttpServletResponse.SC_NOT_FOUND);
405         return new ModelAndView();
406     }
407 
408     /**
409      * Handle the case where an async request timed out.
410      * The default implementation sends an HTTP 503 error.
411      * @param ex the AsyncRequestTimeoutException to be handled
412      * @param request current HTTP request
413      * @param response current HTTP response
414      * @param handler the executed handler, or null if none chosen
415      * at the time of the exception (for example, if multipart resolution failed)
416      * @return an empty ModelAndView indicating the exception was handled
417      * @throws IOException potentially thrown from HttpServletResponse#sendError
418      * @since 4.2.8
419      */
420     protected ModelAndView handleAsyncRequestTimeoutException(AsyncRequestTimeoutException ex,
421             HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) 
422             throws IOException {
423 
424         if (!response.isCommitted()) {
425             response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
426         }
427         else {
428             logger.warn("Async request timed out");
429         }
430         return new ModelAndView();
431     }
432 
433     /**
434      * Invoked to send a server error. Sets the status to 500 and also sets the
435      * request attribute "javax.servlet.error.exception" to the Exception.
436      */
437     protected void sendServerError(Exception ex, HttpServletRequest request, 
438             HttpServletResponse response) throws IOException {
439 
440         request.setAttribute("javax.servlet.error.exception", ex);
441         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
442     }
443 
444 }
View Code

是Spring MVC的前端控制器DispatcherServlet缺省启用的HandlerExceptionResolver之一,并且被设置为最低优先级;是Spring MVC配置机制WebMvcConfigurationSupport缺省定义的一个HandlerExceptionResolver。

异常与HTTP状态码的对应关系如下:

 异常间的关系

框架默认添加的ExceptionResolver

(参阅:https://blog.csdn.net/andy_zhang2007/article/details/88594332

Spring MVC的缺省配置类WebMvcConfigurationSupport定义了一组HandlerExceptionResolver组件到容器,供Spring MVC运行时使用,具体来讲,是被DispatcherServlet使用。

在实现上,是将框架默认的或开发者自定义添加的HandlerExceptionResolver列表包装成HandlerExceptionResolverComposite并返回。见WebMvcConfigurationSupport.handlerExceptionResolver方法,其将框架默认的或用户自定义的Resolver加入其中。相关源码:

 1     public HandlerExceptionResolver handlerExceptionResolver() {
 2         List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
 3         configureHandlerExceptionResolvers(exceptionResolvers);// 让子类指定自己的HandlerExceptionResolver到exceptionResolvers
 4         if (exceptionResolvers.isEmpty()) {
 5     addDefaultHandlerExceptionResolvers(exceptionResolvers);// 若子类未自己指定则框架添加默认的HandlerExceptionResolver到exceptionResolvers
 6         }
 7         extendHandlerExceptionResolvers(exceptionResolvers);// 让子类指定更多自己的HandlerExceptionResolver到exceptionResolvers,或修改已由的exceptionResolver内容
 8         HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
 9         composite.setOrder(0);
10         composite.setExceptionResolvers(exceptionResolvers);
11         return composite;
12     }
View Code

默认情况下SpringMVC框架会添加第1、2、4个resolver,添加的逻辑在WebMvcConfigurationSupport.addDefaultHandlerExceptionResolvers方法中,相关源码:

 1     protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
 2         ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
 3         exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager());
 4         exceptionHandlerResolver.setMessageConverters(getMessageConverters());
 5         exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
 6         exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
 7         if (jackson2Present) {
 8             exceptionHandlerResolver.setResponseBodyAdvice(
 9                     Collections.singletonList(new JsonViewResponseBodyAdvice()));
10         }
11         if (this.applicationContext != null) {
12             exceptionHandlerResolver.setApplicationContext(this.applicationContext);
13         }
14         exceptionHandlerResolver.afterPropertiesSet();
15         exceptionResolvers.add(exceptionHandlerResolver);
16 
17         ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
18         responseStatusResolver.setMessageSource(this.applicationContext);
19         exceptionResolvers.add(responseStatusResolver);
20 
21         exceptionResolvers.add(new DefaultHandlerExceptionResolver());
22     }
View Code

  

SpringMVC中的全局异常处理

通常可通过实现HandlerExceptionResolver接口并声明为Bean即可实现“全局”异常拦截,但实际上这里的“全局”并非真的全局,其只能处理Controller中的handler方法(或其调用的方法)抛出的异常,对于其他非Controller handler方法调用产生的异常,如404、Servlet filter所抛的异常(如Spring Security authenticateFail时抛的异常)则无法处理。

那如何做到真正的全局?这就需要了解下SpringBoot中的异常处理机制。

关键类:

HandlerExceptionResolver:SpringMVC定义的处理handler异常的接口抽象。

DefaultErrorAttributes:SpringBoot定义的处理错误属性的默认类,ErrorAttributes接口的默认实现。默认的SpringBoot异常信息包含error、timestamp、status、message等属性即由此定义。

BasicErrorController:SpringBoot定义的用于处理异常的类。

1) 对于非Controller handler所抛的异常

web应用容器(如Tomcat)会在发生异常后将异常记录到request attribute(request.setAttribute)中,并将当前请求forward到错误页面(默认是"/error")。记录到request的属性(相关定义在javax.servlet.RequestDispatcher接口中)包括:

javax.servlet.error.request_uri:String类型

javax.servlet.error.exception:Throwable类型

javax.servlet.error.message:String类型

javax.servlet.error.status_code:Integer类型

相关源码:

1     private void exception(Request request, Response response,
2                            Throwable exception) {
3         request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, exception);
4         response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
5         response.setError();
6     }
View Code

而SpringBoot中有个BasicErrorController,专门用来处理对错误页面的请求。其主要逻辑是从DefaultErrorAttributes获取记录到request的attribute信息(DefaultErrorAttributes.getErrorAttributes)并包装成自定义的格式返回,该格式包括timestamp、status、error、message、path等字段。当项目出现异常且我们未做任何异常处理时,我们会发现返回了json格式的数据,就是这里起的作用。相关源码:

 1 //BasicErrorController
 2 
 3 
 4     @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
 5     public ModelAndView errorHtml(HttpServletRequest request,
 6             HttpServletResponse response) {
 7         HttpStatus status = getStatus(request);
 8         Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
 9                 request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
10         response.setStatus(status.value());
11         ModelAndView modelAndView = resolveErrorView(request, response, status, model);
12         return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
13     }
14 
15     @RequestMapping
16     public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
17         Map<String, Object> body = getErrorAttributes(request,
18                 isIncludeStackTrace(request, MediaType.ALL));
19         HttpStatus status = getStatus(request);
20         return new ResponseEntity<>(body, status);
21     }
View Code

2) 对于Conotroller handler所抛异常

由注册到Spring容器的各HandlerExceptionResolver Bean来处理的。

值得注意的是,默认情况下,DefaultErrorAttributes也会被注册为一个HandlerExceptionResolver Bean(因为其也实现了HandlerExceptionResolver接口)且该Bean优先级最高!DefaultErrorAttributes的resolveException逻辑是将当前异常绑定到request以供后面的HandlerExceptionResolver处理。相关源码:

 1 //DefaultErrorAttributes
 2 
 3     @Override
 4     public ModelAndView resolveException(HttpServletRequest request,
 5             HttpServletResponse response, Object handler, Exception ex) {
 6         storeErrorAttributes(request, ex);
 7         return null;
 8     }
 9 
10     private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
11         request.setAttribute(ERROR_ATTRIBUTE, ex);
12     }
View Code

综上可知,为了做到真正的异常全局拦截处理,可以实现自定义的ErrorAttributes以替换DefaultErrorAttributes,并重写其resolveException、getErrorAttributes方法来完成,两个方法分别对应Controller handler和非Controller handler异常的处理。

参考资料:https://blog.csdn.net/andy_zhang2007/article/details/89048854 

 

 

HttpMessageConverter

SpringMVC默认使用的HttpMessageConverter:

 

相关配置代码在 WebMvcConfigurationSupport#addDefaultHttpMessageConverters :

static {
        ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();
        
        romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
        
        jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
        
        jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", 
                classLoader) &&    ClassUtils.isPresent(
                "com.fasterxml.jackson.core.JsonGenerator", 
                classLoader);
        
        jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", 
            classLoader);
        
        jackson2SmilePresent = ClassUtils.isPresent(
            "com.fasterxml.jackson.dataformat.smile.SmileFactory", 
            classLoader);
        
        jackson2CborPresent = ClassUtils.isPresent(
            "com.fasterxml.jackson.dataformat.cbor.CBORFactory", 
            classLoader);
        
        gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
        
        jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
    }




/**
     * Adds a set of default HttpMessageConverter instances to the given list.
     * Subclasses can call this method from #configureMessageConverters.
     * @param messageConverters the list to add the default message converters to
     */
    protected final void addDefaultHttpMessageConverters(
        List<HttpMessageConverter<?>> messageConverters) {
        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
        stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316

        messageConverters.add(new ByteArrayHttpMessageConverter());
        messageConverters.add(stringHttpMessageConverter);
        messageConverters.add(new ResourceHttpMessageConverter());
        messageConverters.add(new ResourceRegionHttpMessageConverter());
        try {
            messageConverters.add(new SourceHttpMessageConverter<>());
        }
        catch (Throwable ex) {
            // Ignore when no TransformerFactory implementation is available...
        }
        messageConverters.add(new AllEncompassingFormHttpMessageConverter());

        if (romePresent) {
            messageConverters.add(new AtomFeedHttpMessageConverter());
            messageConverters.add(new RssChannelHttpMessageConverter());
        }

        if (jackson2XmlPresent) {
            Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
            if (this.applicationContext != null) {
                builder.applicationContext(this.applicationContext);
            }
            messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
        }
        else if (jaxb2Present) {
            messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
        }

        if (jackson2Present) {
            Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
            if (this.applicationContext != null) {
                builder.applicationContext(this.applicationContext);
            }
            messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
        }
        else if (gsonPresent) {
            messageConverters.add(new GsonHttpMessageConverter());
        }
        else if (jsonbPresent) {
            messageConverters.add(new JsonbHttpMessageConverter());
        }

        if (jackson2SmilePresent) {
            Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
            if (this.applicationContext != null) {
                builder.applicationContext(this.applicationContext);
            }
            messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
        }
        if (jackson2CborPresent) {
            Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
            if (this.applicationContext != null) {
                builder.applicationContext(this.applicationContext);
            }
            messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
        }
    }
View Code

 

 

 

 

转自:https://www.cnblogs.com/xiaoxi/p/6164383.html,以下为正文。

 

SpringMVC的工作原理图:

SpringMVC流程

1、  用户发送请求至前端控制器DispatcherServlet。

2、  DispatcherServlet收到请求调用HandlerMapping处理器映射器。

3、  处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。

4、  DispatcherServlet调用HandlerAdapter处理器适配器。

5、  HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。

6、  Controller执行完成返回ModelAndView。

7、  HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。

8、  DispatcherServlet将ModelAndView传给ViewReslover视图解析器。

9、  ViewReslover解析后返回具体View。

10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。

11、 DispatcherServlet响应用户。

组件说明:

以下组件通常使用框架提供实现:

DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。

HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。 

HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。

ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。

组件:
1、前端控制器DispatcherServlet(不需要工程师开发),由框架提供
作用:接收请求,响应结果,相当于转发器,中央处理器。有了dispatcherServlet减少了其它组件之间的耦合度。
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。

2、处理器映射器HandlerMapping(不需要工程师开发),由框架提供
作用:根据请求的url查找Handler
HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

3、处理器适配器HandlerAdapter
作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

4、处理器Handler(需要工程师开发)
注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler
Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。
由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。

5、视图解析器View resolver(不需要工程师开发),由框架提供
作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)
View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。
一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。

6、视图View(需要工程师开发jsp...)
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf...)

核心架构的具体流程步骤如下:
1、首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
2、DispatcherServlet——>HandlerMapping, HandlerMapping 将会把请求映射为HandlerExecutionChain 对象(包含一个Handler 处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
3、DispatcherServlet——>HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
4、HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView 对象(包含模型数据、逻辑视图名);
5、ModelAndView的逻辑视图名——> ViewResolver, ViewResolver 将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
6、View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
7、返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。

下边两个组件通常情况下需要开发:

Handler:处理器,即后端控制器用controller表示。

View:视图,即展示给用户的界面,视图中通常需要标签语言展示模型数据。

 

在将SpringMVC之前我们先来看一下什么是MVC模式

MVC:MVC是一种设计模式

MVC的原理图:

分析:

M-Model 模型(完成业务逻辑:有javaBean构成,service+dao+entity)

V-View 视图(做界面的展示  jsp,html……)

C-Controller 控制器(接收请求—>调用模型—>根据结果派发页面)

 

springMVC是什么: 

  springMVC是一个MVC的开源框架,springMVC=struts2+spring,springMVC就相当于是Struts2加上sring的整合,但是这里有一个疑惑就是,springMVC和spring是什么样的关系呢?这个在百度百科上有一个很好的解释:意思是说,springMVC是spring的一个后续产品,其实就是spring在原有基础上,又提供了web应用的MVC模块,可以简单的把springMVC理解为是spring的一个模块(类似AOP,IOC这样的模块),网络上经常会说springMVC和spring无缝集成,其实springMVC就是spring的一个子模块,所以根本不需要同spring进行整合。

SpringMVC的原理图:

看到这个图大家可能会有很多的疑惑,现在我们来看一下这个图的步骤:(可以对比MVC的原理图进行理解)

第一步:用户发起请求到前端控制器(DispatcherServlet)

第二步:前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者注解进行查找

第三步:找到以后处理器映射器(HandlerMappering)像前端控制器返回执行链(HandlerExecutionChain)

第四步:前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)

第五步:处理器适配器去执行Handler

第六步:Handler执行完给处理器适配器返回ModelAndView

第七步:处理器适配器向前端控制器返回ModelAndView

第八步:前端控制器请求视图解析器(ViewResolver)去进行视图解析

第九步:视图解析器像前端控制器返回View

第十步:前端控制器对视图进行渲染

第十一步:前端控制器向用户响应结果

看到这些步骤我相信大家很感觉非常的乱,这是正常的,但是这里主要是要大家理解springMVC中的几个组件:

前端控制器(DispatcherServlet):接收请求,响应结果,相当于电脑的CPU。

处理器映射器(HandlerMapping):根据URL去查找处理器

处理器(Handler):(需要程序员去写代码处理逻辑的)

处理器适配器(HandlerAdapter):会把处理器包装成适配器,这样就可以支持多种类型的处理器,类比笔记本的适配器(适配器模式的应用)

视图解析器(ViewResovler):进行视图解析,多返回的字符串,进行处理,可以解析成对应的页面

推荐阅读