导言

Spring MVC是基于MVC架构(模型-视图-控制器)的高级Web框架,建立在Spring IOC容器之上,底层通过高度整合的组件实现了对Web应用中控制层的完整管理。基于Spring框架的强大基础,致力于提供一个高效、灵活的Web应用开发环境。

Spring MVC的核心组件包括处理器映射器处理器适配器拦截器等,核心组件的设计允许高度的模块化和可拓展性,通过共同协作,扩展并优化了请求的处理流程。

处理器映射器建立请求路径与处理器间的映射关系,返回处理器链对象,保证了请求能被正确分发至相应的处理器;

处理器适配器提供了强大的数据绑定机制,通过参数解析器与返回值处理器,自动将输入输出数据与Java应用的数据模型相连接,极大简化了前后端间流转数据的处理;

拦截器则在此基础上提供了请求处理前后的拦截功能,增加了处理流程的灵活性和安全性。

DispatcherServlet是整个MVC流程的中枢,负责协调各组件的执行,完成对HTTP请求的调度分发。

全局异常处理器处理整个应用中抛出的异常,通过集中处理所有控制器层面的异常,统一管理错误处理逻辑,返回结构化的错误响应,显著提升了程序的健壮性。

核心配置类利用Spring框架的控制反转和依赖注入,整合各个组件,管理和配置框架所需的各种资源和服务;将业务逻辑与配置逻辑分离,为应用的启动和运行提供了统一的配置入口,使得整个系统更加简洁和模块化。

整体架构

(蓝色为已经淘汰的JSP技术,前后端分离开发模式下并不用考虑)

spring_mvc

1. 处理器映射器

  1. 处理器映射器-建立映射关系
  2. 拦截器
  3. 处理器映射器-返回处理器链对象

2. 处理器适配器

  1. 参数解析器
  2. 返回值处理器
  3. Handler执行器
  4. 处理器适配器

3. 视图解析器

  1. 视图的渲染
  2. 视图解析器

4. DispatcherServlet

5. 全局异常处理器

6. 核心配置类

<span style="color:red">当核心类看不懂的时候看看接口,接口定义了最核心的功能要求,能够从更高的视角观察代码底层的主要逻辑

设计模式

1. 代理模式

拦截器:控制对另一个拦截器的访问,基于URL模式的匹配结果来决定是否调用实际拦截器的方法

2. 责任链模式

拦截器链的设计用到了责任链模式。

责任链模式:允许多个对象处理一个请求,而发送请求的客户端并不需要知道请求的具体处理者是谁。每个处理器都有机会处理请求,并且有责任决定自己是否能处理该请求。请求沿着一条链传递,从一个对象传递到下一个对象,直到有对象处理该请求为止。

在请求传递时,每个拦截器都扮演责任链中的一个节点,拦截器链按照顺序调用多个拦截器,每个拦截器会检查或处理进来的请求,并决定是否将请求传递给链中的下一个拦截器。

3. 策略模式

参数解析器返回值处理器

都是通过复合类,底层自动根据参数匹配合适的解析器方法

处理器映射器-建立映射关系

RequestMappingHandlerMapping初始化过程

实现了 HandlerMapping的初始化, 了解到了 Controller中的方法是如何转换成 HandlerMethod

在应用上下文配置环境执行 refresh()方法时,建立起映射关系

1. 组成组件

RequestMapping注解

1
2
3
4
5
6
7
8
9
10
11
12
@Target({ElementType.TYPE, ElementType.METHOD}) // 类&方法上
@Retention(RetentionPolicy.RUNTIME)
@Documented // 用于指示其他注解是否应该包含在 Java 文档中
public @interface RequestMapping {

// 表示url中的路径
String path();

// 表示http请求的方式
RequestMethod method() default RequestMethod.GET;

}

RequestMethod枚举类,定义了请求方法

1
2
3
public enum RequestMethod {
GET, POST
}

RequestMappingInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 表示请求映射信息的类,包含了一个关于请求映射的所有信息
* 如url路径、http请求方法、请求参数、请求头等
*/
public class RequestMappingInfo {

// url路径
private String path;

// http请求方法
private RequestMethod httpMethod;

public RequestMappingInfo(String prefix, RequestMapping requestMapping) {
this.path = prefix + requestMapping.path();
this.httpMethod = requestMapping.method();
}

//getter setter...
}

HandlerMethod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 处理程序方法的类
* 用于根据信息执行对应方法
*/
public class HandlerMethod {

// 表示处理程序方法的对象
private Object bean;

// 方法所属的类
private Class<?> beanType;

// 方法对象
private Method method;

// 方法参数对象列表
private List<MethodParameter> parameters;

public HandlerMethod(Object bean, Method method) {
this.bean = bean;
this.beanType = bean.getClass();
this.method = method;

this.parameters = new ArrayList<>();
int parameterCount = method.getParameterCount();
for (int index = 0; index < parameterCount; index++) {
parameters.add(new MethodParameter(method, index));
}
}
}

MappingRegistry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 所有映射(RequestMappingInfo,HandlerMethod)的注册中心
*/
public class MappingRegistry {

// 存储url与RequestMappingInfo之间的映射关系
private Map<String, RequestMappingInfo> pathMappingInfo = new ConcurrentHashMap<>();

// 存储url与HandlerMethod对象之间的映射关系
private Map<String, HandlerMethod> pathHandlerMethod = new ConcurrentHashMap<>();


/**
* 注册url和Mapping/HandlerMethod的对应关系
*
* @param mapping 表示请求映射信息,如url路径、http请求方法、请求参数、请求头等
* @param handler 表示处理程序方法的对象,即要执行方法的bean
* @param method 要执行类的方法对象
*/
public void register(RequestMappingInfo mapping, Object handler, Method method) {
pathMappingInfo.put(mapping.getPath(), mapping);

HandlerMethod handlerMethod = new HandlerMethod(handler, method);
pathHandlerMethod.put(mapping.getPath(), handlerMethod);
}

//getter setter...
}

2. 核心类&执行流程

2.1 概述

RequestMappingHandlerMapping处理Http请求映射的核心组件

负责管理请求映射信息,以实现客户端请求到处理程序方法的路由和分发

2.2 具体步骤&类详解

  1. 解析 @RequestMapping注解,获取请求信息
  2. 管理请求映射信息:将请求路径与处理程序方法 HandlerMethod建立映射管理
  3. 在收到 HTTP 请求时,根据请求路径和请求方法找到相应的处理程序方法,以便执行具体的请求处理逻辑

类关系(继承与实现)

extends ApplicationObjectSupport

该抽象类里面实现了 ApplicationAware接口,在这里的主要是使 RequestMappingHandlerMapping感知 ApplicationContext应用上下文,获取容器中所有bean

implements HandlerMapping, InitializingBean

别把 InitializingBean接口忘了,跟 init_method一个效果,重写接口中的方法用于插入初始化方法!

1
2
3
4
5
6
7
8
9
private void initialHandlerMethods() {
// 1. 获取Spring容器中所有的bean及其beanName
Map<String, Object> beansOfMap = BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), Object.class);
beansOfMap.entrySet().stream()
// 2. 过滤出所有带@Controller注解的类
.filter(entry -> this.isHandler(entry.getValue()))
// 3. 查找@RequestMapping注解,自动发现并注册http请求处理方法
.forEach(entry -> this.detectHandlerMethods(entry.getKey(), entry.getValue()));
}

isHandler()方法内部使用了 AnnotatedElementUtils.hasAnnotation

detectHandlerMethods()方法内部遍历所有bean,处理 @RequestMapping注解,封装为 RequestMappingInfo对象,然后将其注册进 mappingRegistry中,从而将请求路径与处理程序方法 HandlerMethod建立映射关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private void detectHandlerMethods(String beanName, Object handler) {
Class<?> beanType = handler.getClass();
Map<Method, RequestMappingInfo> methodsOfMap = MethodIntrospector.selectMethods(beanType,
// 函数式接口,检查每一个方法,如果方法上有@RequestMapping注解,返回RequestMappingInfo实例
(MethodIntrospector.MetadataLookup<RequestMappingInfo>) method -> getMappingForMethod(method, beanType));
// 注册映射,将方法、处理器和请求映射信息注册到MappingRegistry
methodsOfMap.forEach((method, requestMappingInfo) -> this.mappingRegistry.register(requestMappingInfo, handler, method));
}

/**
* 查找method上面是否有RequestMapping,有 => 构建RequestMappingInfo
*
* @param method
* @param beanType
* @return
*/
private RequestMappingInfo getMappingForMethod(Method method, Class<?> beanType) {
// 使用AnnotatedElementUtils.findMergedAnnotation来查找方法上的@RequestMapping注解,返回值为具体@RequestMapping注解
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
if (Objects.isNull(requestMapping)) {
return null;
}
String prefix = getPathPrefix(beanType);
// 返回RequestMappingInfo实现类,RequestMappingInfo构造方法中会自动拼接路径: 类path+(方法path+方法对象)==(类path+方法path)+方法对象
return new RequestMappingInfo(prefix, requestMapping);
}

private String getPathPrefix(Class<?> beanType) {
// 使用AnnotatedElementUtils.findMergedAnnotation查找类上是否有@RequestMapping注解
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(beanType, RequestMapping.class);
if (Objects.isNull(requestMapping)) {
return "";
}
// 如果有的话,取出path属性作为请求映射的路径前缀
return requestMapping.path();
}

2.3 如何集成进上下文环境

上面所有的逻辑都被放入到 InitializingBeanafterPropertiesSet()中,交由 ApplicationContext#refresh()统一管理,从而实现集成!

1
2
3
4
5
// implements InitializingBean
@Override
public void afterPropertiesSet() throws Exception {
initialHandlerMethods();
}

3. 两个工具类

使用了两个工具类的三个方法

BeanFactoryUtils

  • beansOfTypeIncludingAncestors()获取Spring容器中所有的bean及其beanName

AnnotatedElementUtils

  • hasAnnotation()过滤出所有带 @Controller注解的类

    • 原理还是回到了 findAnnotations()
  • findMergedAnnotation()

    • 两个参数:

      • element-带注解的元素
      • annotationType-要查找的注解类型
    • 返回值:

      • 合并的、合成的注解实例,或者null
    • 原理:

      • 内部先通过 AnnotationFilter匹配注解,然后调用 getDeclaredAnnotation()获取注解实例,再底层就涉及到了反射获取注解。
      • 返回合并的注解(继承和覆盖),原理太复杂,没看

4. 合并注解的原理及意义

by ChatGPT4

合并注解的原理是 Spring Framework 中用于处理注解继承、重写和组合的一种高级技术。它主要涉及到注解的合并策略,以及如何处理由多个注解或其属性构成的复杂关系。在 Spring 中,这个机制主要通过几个核心组件来实现:

核心组件和类

  1. **AnnotatedElementUtils**:这个类提供了静态方法来搜索带注解的元素(如类、方法或字段),并考虑了注解的继承关系和元注解。
  2. MergedAnnotations API:从 Spring 5.2 开始,Spring 引入了 MergedAnnotations API,这是一个用于从类或方法等元素中获取注解信息的高性能、低内存占用的API。这个 API 能够处理注解的合并,并解析注解的层次结构。
  3. **MergedAnnotation**:表示一个可能来自多个源的合并后的注解。它不仅包含注解本身的信息,还包括合并过程中的元数据,如注解是否是显式声明的、继承的或通过元注解合成的。

合并原理的步骤

合并注解的过程可以分为以下几个步骤:

  1. 收集注解:从给定的 AnnotatedElement(可以是类、方法、字段等)收集所有相关的注解,包括直接声明的注解和通过继承获得的注解。
  2. 处理元注解:元注解是应用于其他注解的注解。例如,@Transactional 可以用 @MetaAnnotation 标记。在处理一个注解时,Spring 也会查找其元注解,并将这些注解的属性合并到主注解中。
  3. 属性合并:如果一个注解在继承链上多次出现,或者通过不同的元注解多次指定,Spring 需要确定哪些属性值将被采用。通常,更具体的定义(如在子类上的定义)会覆盖更一般的定义(如在父类上的定义)。
  4. 属性别名处理:在注解定义中,可以通过 @AliasFor 指明属性别名,表明两个属性是等价的。在处理合并时,Spring 会确保别名之间的一致性,如果一个属性被设置了,它的别名也会相应地被设置。
  5. 最终合成:通过上述步骤,Spring 构建了一个包含了所有合并逻辑的注解视图。最后,这个合并后的注解可以被应用于目标对象(如 Bean 定义),影响其行为。

目前来看意义在于灵活处理注解继承关系、元注解信息、多个注解同时使用怎么处理

拦截器

1. 核心类

image-HandlerInterceptor

HandlerInterceptor接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 定义了拦截和处理http请求前置、后置及完成后的操作的方法
*/
public interface HandlerInterceptor {

/**
* 在实际的处理器(Controller)被调用之前执行
* @param request 请求对象
* @param response 响应对象
* @param handler 表示当前被拦截的处理器
* @return true正常执行,false停止后续执行,自己处理响应
* @throws Exception
*/
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}

/**
* 在处理器(Controller)执行之后、生成试图之前调用
* @param request 请求对象
* @param response 响应对象
* @param handler 表示当前被拦截的处理器
* @param modelAndView 表示处理器返回的ModelAndView对象
* @throws Exception
*/
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}

/**
* 在整个请求处理完毕之后调用,及在视图渲染完毕后
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}

MappedInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* 用于精确控制拦截器的激活
* MappedInterceptor不直接处理拦截任务,而是将请求委托给另一个HandlerInterceptor实例
* MappedInterceptor本身主要负责决定是否执行拦截,实际的拦截逻辑由委托的HandlerInterceptor完成
*/
public class MappedInterceptor implements HandlerInterceptor {

/**
* 定义了哪些url模式应该被拦截器处理
*/
private List<String> includePatterns = new ArrayList<>();

/**
* 定义了哪些url模式应该从拦截器处理中排除
*/
private List<String> excludePatterns = new ArrayList<>();

/**
* 用于存储被MappedInterceptor封装的拦截器实例
*/
private HandlerInterceptor interceptor;

/**
* 接收一个HandlerInterceptor对象,用于后续的请求处理
* @param interceptor
*/
public MappedInterceptor(HandlerInterceptor interceptor) {
this.interceptor = interceptor;
}

public HandlerInterceptor getInterceptor() {
return interceptor;
}

/**
* 添加支持的path
* @param patterns
* @return
*/
public MappedInterceptor addIncludePatterns(String... patterns) {
this.includePatterns.addAll(Arrays.asList(patterns));
return this;
}

/**
* 添加排除的path
* @param patterns
* @return
*/
public MappedInterceptor addExcludePatterns(String... patterns) {
this.excludePatterns.addAll(Arrays.asList(patterns));
return this;
}


/**
* path匹配逻辑
* @param lookupPath
* @return
*/
public boolean matches(String lookupPath) {
// 1. 先检查url在不在排除列表中
if (!CollectionUtils.isEmpty(this.excludePatterns)) {
// 如果url在排除列表中,则不启用拦截器
if (excludePatterns.contains(lookupPath)) {
return false;
}
}
// 2. 如果不在排除列表中
// a. 检查包含列表,若为空,则拦截所有路径
if (ObjectUtils.isEmpty(this.includePatterns)) {
return true;
}
// b. 不为空,那就拦截包含列表中的路径
if (includePatterns.contains(lookupPath)) {
return true;
}
// c. 剩下的都放行
return false;
}


@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return this.interceptor.preHandle(request, response, handler);
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
this.interceptor.postHandle(request, response, handler, modelAndView);
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
this.interceptor.afterCompletion(request, response, handler, ex);
}
}

InterceptorRegistry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 拦截器注册中心
*/
public class InterceptorRegistry {
private List<MappedInterceptor> mappedInterceptors = new ArrayList<>();

/**
* 注册一个拦截器到Registry
* @param interceptor
* @return
*/
public MappedInterceptor addInterceptor(HandlerInterceptor interceptor) {
MappedInterceptor mappedInterceptor = new MappedInterceptor(interceptor);
mappedInterceptors.add(mappedInterceptor);
return mappedInterceptor;
}

public List<MappedInterceptor> getMappedInterceptors() {
return mappedInterceptors;
}
}

2. 代理模式(静态代理)

2.1 关系

代理类 MappedInterceptor持有一个真实对象的引用 interceptor(开发者自己实现的拦截器),并可以在调用真实对象的方法前后执行额外的操作。

2.2 设计的核心目的

控制对另一个拦截器的访问,基于URL模式的匹配结果来决定是否调用实际拦截器的方法。这种行为是典型的代理模式应用。

2.3 代理模式特点的体现

  1. 在原本对象的基础上增加额外:控制访问,前置条件检查
  2. 不改变接口:在外部看来仍然是一个拦截器,与代理模式定义相符

隐含策略模式

将url匹配逻辑封装为可配置的策略,自动根据不同的需求匹配拦截策略

处理器映射器-返回处理器链对象

HandlerMapping获取对应的 Handler

image-Handler-Mapping---Handler

HandlerExecutionChain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
public class HandlerExecutionChain {

// 处理http请求的方法,封装在HandlerMethod对象中
private HandlerMethod handler;

// 拦截器列表
private List<HandlerInterceptor> interceptors = new ArrayList<>();

// 当时执行的拦截器索引,用于在请求处理过程中跟踪拦截器的执行位置
private int interceptorIndex = -1;

/**
* 构造函数
* @param handler 封装程序方法的类,用来执行程序方法
* @param interceptors 拦截器列表
*/
public HandlerExecutionChain(HandlerMethod handler, List<HandlerInterceptor> interceptors) {
this.handler = handler;
if (!CollectionUtils.isEmpty(interceptors)) {
this.interceptors = interceptors;
}
}

/**
* 遍历拦截器列表执行每个拦截器的perHandle方法
* @param request
* @param response
* @return
* @throws Exception
*/
public boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (CollectionUtils.isEmpty(interceptors)) {
return true;
}
for (int i = 0; i < interceptors.size(); i++) {
HandlerInterceptor interceptor = interceptors.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}

/**
* **逆序**遍历拦截器列表执行每个拦截器的postHandle方法
* @param request
* @param response
* @param mv
* @throws Exception
*/
public void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
if (CollectionUtils.isEmpty(interceptors)) {
return;
}
for (int i = interceptors.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}

/**
* 逆序遍历并调用每个拦截器的afterCompletion方法
* afterCompletion,后续处理,在整个请求处理完毕之后调用,及在视图渲染完毕后
* @param request
* @param response
* @param ex
* @throws Exception
*/
public void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {
if (CollectionUtils.isEmpty(interceptors)) {
return;
}
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors.get(i);
interceptor.afterCompletion(request, response, this.handler, ex);
}
}

public List<HandlerInterceptor> getInterceptors() {
return interceptors;
}

public HandlerMethod getHandler() {
return handler;
}
}

1. 拦截器执行顺序

这些行为的设计主要基于Spring MVC拦截器的逻辑执行流程,旨在确保资源的正确管理和对请求处理的精确控制。以下是详细解释:

1.1 为什么多个拦截器的三个方法(preHandle, postHandle, afterCompletion)要以特定的顺序执行?

  • preHandle 方法:在控制器(Controller)方法处理之前执行。这是预处理请求的阶段,拦截器可以决定是否继续执行处理链(即继续调用其他拦截器或最终的处理器)。如果任何一个 preHandle 方法返回 false,则整个请求处理流程会立即停止,这允许拦截器阻止进一步的处理,比如在用户未授权访问特定资源时。
  • postHandle 方法:仅当所有的 preHandle 方法返回 true,即请求已被完全处理且未被任何拦截器拦截时,postHandle 方法才会被执行。它在控制器方法执行完毕后、视图渲染之前执行,用于修改或增加模型数据等后处理操作。
  • afterCompletion 方法:无论请求处理的结果如何,都将执行(即使有异常抛出)。这在处理链中的最后阶段执行,通常用于清理资源,如在请求开始时打开的资源。

1.2 为什么当 preHandle 方法返回 false 后,会跳过所有 postHandle 方法的执行?

preHandle 方法返回 false

  • 表示拦截器已经确定后续的处理器方法不应继续执行,可能是因为用户未通过身份验证、请求数据不符合要求等原因。
  • 跳过后续的所有处理器以及它们的 postHandle 方法执行是为了阻止进一步处理请求,因为后续步骤依赖于前面步骤的成功完成。继续执行 postHandle 可能会引起不一致的状态,因为 postHandle 方法通常假定 preHandle 已经成功执行并进行了相应的处理。
  • 跳过 postHandle 后直接执行 afterCompletion 方法,是因为尽管请求没有被完全处理,但可能需要执行某些清理工作,这是 afterCompletion 方法的设计目的。

这种设计模式确保了请求处理的一致性和预测性,使开发者能够在不同阶段精确控制请求的处理行为,同时在出现问题时能够及时中断请求处理,避免不必要的处理开销和潜在的错误。

2. getHandler()方法

此方法主要负责根据传入的请求(通常是一个 HttpServletRequest 对象),找到对应的处理器(handler)。这个处理器通常是一个标有 @RequestMapping 注解的方法,如果找到匹配的处理器,它会返回一个 HandlerExecutionChain 对象。

总之,该方法是用于查找和返回处理特定请求所需的全部信息(包括处理器和拦截器)的复杂过程。

HandlerMapping接口

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 用于将客户端发起的 HTTP 请求映射到处理该请求的处理程序
*/
public interface HandlerMapping {
/**
* 根据给定的 HttpServletRequest 对象获取相应的处理程序执行链
* @param request 给定的 HttpServletRequest 对象
* @return 获取相应的处理程序执行链
* @throws Exception
*/
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

RequestMappingHandlerMapping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 拦截器对象列表
private List<MappedInterceptor> interceptors = new ArrayList<>();
// 外部代码设置拦截器列表
public void setInterceptors(List<MappedInterceptor> interceptors) {
this.interceptors = interceptors;
}

/**
* 根据给定的 HttpServletRequest 对象获取相应的处理程序执行链
* @param request 给定的 HttpServletRequest 对象
* @return 获取相应的处理程序执行链
* @throws Exception
*/
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 获取到本次请求的path
String lookupPath = request.getRequestURI();
// 根据path从MappingRegistry中找到对应的HandlerMethod
HandlerMethod handler = mappingRegistry.getHandlerMethodByPath(lookupPath);
// 找不到的话抛出NoHandlerFoundException异常
if (Objects.isNull(handler)) {
throw new NoHandlerFoundException(request);
}
// 找到了处理方法,则创建处理执行链(包含处理方法和HandlerExecutionChain)
return createHandlerExecutionChain(lookupPath, handler);
}
/**
* 创建处理执行链
* @param lookupPath
* @param handler
* @return
*/
private HandlerExecutionChain createHandlerExecutionChain(String lookupPath, HandlerMethod handler) {
// 筛选出与当前请求路径匹配的拦截器
List<HandlerInterceptor> interceptors = this.interceptors.stream()
.filter(mappedInterceptor -> mappedInterceptor.matches(lookupPath))
.collect(toList());
// 调用构造函数创建一个执行链实例
return new HandlerExecutionChain(handler, interceptors);
}

还有两个异常类

ServletException

NoHandlerFoundException

3. Handler

Handler:处理程序

3.1 Spring MVC中的Handler

在Spring MVC框架中,一个Handler通常指代一个处理HTTP请求的方法(即控制器方法)。这个方法能够接收请求、处理业务逻辑,并返回响应。

Spring MVC使用“Controller”这一术语来描述包含一个或多个Handler方法的类。

每个Handler方法通过注解(如 @RequestMapping@GetMapping@PostMapping 等)与一个特定的HTTP请求路径或动作关联。

3.2 Handler的职责

Handler的基本职责是接收HTTP请求,执行适当的业务逻辑处理,并返回适当的响应。响应可以是HTML页面、JSON数据、XML数据等,依据应用程序的需求而定。

3.3 Handler与其他组件的交互

在Spring MVC架构中,Handler通常不单独工作。它们是多个组件交互的一部分,其中包括:

  • DispatcherServlet:作为前端控制器,它负责接收所有请求并将它们分派到相应的Handler。
  • HandlerMapping:决定哪个Handler应当处理一个给定的请求。
  • HandlerAdapter:帮助DispatcherServlet调用Handler方法。
  • ViewResolver:帮助解析Handler返回的视图名到具体的视图(如JSP)。
  • Interceptors:提供了一种机制,允许在处理请求之前和之后(甚至完成之后)执行特定的代码。
  • HandlerExecutionChain:主要作用是为请求处理提供了一个包含Handler和所有应用到这个Handler的拦截器的容器(执行链)。这样,DispatcherServlet 可以按顺序执行所有相关的拦截器逻辑和最终的Handler方法。

通过这些组件的协作,Spring MVC可以灵活地处理各种HTTP请求,同时提供清晰的业务逻辑分离和资源管理。

参数解析器

image-minispringmvc5

1. 组成组件

HttpServletRequest,封装HTTP请求报文

HttpServletResponse,封装HTTP响应报文

Model,用于在控制器和视图之间传递数据,适用于MVC模式下的数据传递和视图渲染

Model是一个在控制器和视图之间传递数据的容器,扮演桥梁的角色,用于存储控制器产生的数据,这些数据最终会被传递到视图层,并在视图层中被渲染到最终的HTML中。Model本身只是作为数据的中介,不直接参与HTTP报文的构成。

用到的注解

  1. @RequestParam

    此注解用在请求handler方法的参数上,用于将http请求参数的值绑定到参数上。

  2. @RequestBody

    此注解用在请求handler方法的参数上,用于将http请求的Body映射绑定到此参数上。HttpMessageConverter负责将对象转换为http请求。

Spring MVC的参数解析机制

  1. HandlerMethodArgumentResolver接口

    定义了处理器方法参数解析器的基本合同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 用于解析控制器方法参数
* <p>负责从HTTP请求中提取数据,并将其转换为控制器方法参数所需的形式
* <p>通过实现此接口,开发者可以控制如何从HTTP请求中提取数据并转换成控制器方法所需要的参数类型,增加了框架的灵活性和强大的扩展能力
*/
public interface HandlerMethodArgumentResolver {

/**
* 这个方法用于判断当前的实现是否支持给定的控制器方法参数。每个参数解析器可能只处理特定类型或具有特定注解的参数。例如,一个专门处理带有 @RequestParam 注解的参数的解析器将会检查传入的 MethodParameter 是否具有该注解。
* @param parameter 对控制器方法中一个参数的封装,包括参数的类型、泛型信息、注解等
* @return 如果这个解析器能处理传入的参数,返回 true;否则,返回 false。
*/
boolean supportsParameter(MethodParameter parameter);

/**
* 负责实际解析HTTP请求,并返回转换后的参数值,该值将用于控制器方法的调用。
* 这个过程可能涉及从请求中提取数据、数据类型转换、以及应用自定义的业务逻辑。
* @param parameter 控制器方法中的参数描述。
* @param request 当前的HTTP请求对象,包含了请求的URL、参数、头信息等。
* @param response 当前的HTTP响应对象,可以用于设置响应的状态码、头信息等。
* @param container 一个容器对象,用于在解析过程中存储模型数据或控制视图的渲染。
* @param conversionService 一个类型转换服务,用于将请求中的字符串类型数据转换为方法参数所需的具体类型。
* @return 解析后的参数值,将直接传递给控制器方法。该值将用于控制器方法的调用。
* @throws Exception
*/
Object resolveArgument(MethodParameter parameter, HttpServletRequest request,
HttpServletResponse response, ModelAndViewContainer container,
ConversionService conversionService) throws Exception;
}
  1. ServletRequestMethodArgumentResolver

    专门解析 HttpServletRequest类型的参数

  2. ServletResponseMethodArgumentResolver

    专门解析 HttpServletResponse类型的参数

  3. ModelMethodArgumentResolver

    用于解析 Model类型的参数。

  4. RequestParamMethodArgumentResolver

    用于解析带有 @RequestParam注解的方法参数。它可以从请求参数中提取值并将其转换为方法参数的类型。这个解析器支持基本类型、复杂对象和带默认值的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
public Object resolveArgument(MethodParameter parameter, HttpServletRequest request, HttpServletResponse response,
ModelAndViewContainer container,
ConversionService conversionService) throws Exception {
// 1. 获取注解信息
RequestParam param = parameter.getParameterAnnotation(RequestParam.class);
if (Objects.isNull(param)) {
return null;
}
// 2. 从请求中提取参数值
String value = request.getParameter(param.name());
// 3. 如果请求中没有提供值,那么使用默认值
if (StringUtils.isEmpty(value)) {
value = param.defaultValue();
}
// 4. 如果成功获取到值,使用ConversionService将字符串值转换为方法参数所需的类型
if (!StringUtils.isEmpty(value)) {
return conversionService.convert(value, parameter.getParameterType());
}
// 5. 如果参数必须且值为空(且无默认值),则抛出MissingServletRequestParameterException异常
if (param.required()) {
throw new MissingServletRequestParameterException(parameter.getParameterName(),
parameter.getParameterType().getName());
}
return null;
}
  1. RequestBodyMethodArgumentResolver

    用于解析带有 @RequestBody注解的方法参数。它从请求体中读取数据并将其转换为方法参数的类型。这个解析器通常用于处理JSON或XML格式的请求体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Override
public Object resolveArgument(MethodParameter parameter, HttpServletRequest request, HttpServletResponse response,
ModelAndViewContainer container,
ConversionService conversionService) throws Exception {
// 1. 获取字符串类型的HTTP请求体
String httpMessageBody = this.getHttpMessageBody(request);
// 2. 使用FastJSON将请求体解析为指定参数类型的对象
if (!StringUtils.isEmpty(httpMessageBody)) {
return JSON.parseObject(httpMessageBody, parameter.getParameterType());
}
// 3. 获取参数上的@RequestBody注解,检查注解是否存在&必须,必须且不存在,抛出MissingServletRequestParameterException异常
RequestBody requestBody = parameter.getParameterAnnotation(RequestBody.class);
if (Objects.isNull(requestBody)) {
return null;
}
if (requestBody.required()) {
throw new MissingServletRequestParameterException(parameter.getParameterName(),
parameter.getParameterType().getName());
}
return null;
}
private String getHttpMessageBody(HttpServletRequest request) throws IOException {
// 用于构建请求体字符串
StringBuilder sb = new StringBuilder();
// 获取请求体的读取器
BufferedReader reader = request.getReader();
// 读取请求体内容并将其追加到StringBuilder中,最后返回整个请求体字符串
char[] buff = new char[1024];
int len;
while ((len = reader.read(buff)) != -1) {
sb.append(buff, 0, len);
}
return sb.toString();
}
  1. HandlerMethodArgumentResolverComposite

    复合类,用于存储和管理多个 HandlerMethodArgumentResolver实例。它允许将多个解析器组合在一起,并根据参数类型或注解自动选择合适的解析器来处理参数。(策略模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 用来存储多个HandlerMethodArgumentResolver实例,使该类管理和使用多个解析器
private List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();

// 解析参数方法
@Override
public Object resolveArgument(MethodParameter parameter, HttpServletRequest request, HttpServletResponse response,
ModelAndViewContainer container, ConversionService conversionService) throws Exception {
// 1. 遍历argumentResolvers列表中的所有解析器
for (HandlerMethodArgumentResolver resolver : argumentResolvers) {
// 2. 调用每个解析器的supportsParameter方法检查其是否支持当前参数
if (resolver.supportsParameter(parameter)) {
// 3. 如果支持,则调用该解析器的resolveArgument方法来解析参数并返回结果
return resolver.resolveArgument(parameter, request, response, container, conversionService);
}
}
// 4. 如果没有任何解析器支持该参数,则抛出非法参数异常
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}

2. 前后端对接过程

在Spring MVC等服务器端渲染的Web应用框架中,视图层是位于服务器端的一部分。这里的视图层不是指前端的HTML、CSS或JavaScript文件(虽然它们最终会生成这些内容),而是指用于生成HTML页面的模板和逻辑。这些模板使用服务器端存储的数据来渲染最终的HTML内容,然后这个内容通过HTTP响应发送给客户端。整个过程可以这样描述:

2.1 过程详解

  1. 用户交互:用户在浏览器中发起一个请求,比如点击一个链接或提交一个表单。
  2. HTTP请求:这个请求以HTTP报文的形式发送到服务器。
  3. 控制器处理:服务器的Spring MVC框架接收到这个HTTP请求,并根据路由信息将请求分派到相应的控制器方法。
  4. 数据处理与Model填充:控制器执行相应的业务逻辑(如数据库查询等),并将结果数据填充到Model对象中。
  5. 视图渲染:控制器返回一个视图名称,根据这个名称,Spring MVC找到相应的视图模板。视图模板使用Model中的数据来渲染生成HTML内容。
  6. HttpServletResponse处理:生成的HTML内容通过 HttpServletResponse对象写入响应体。此外,HttpServletResponse还用于设置HTTP响应的其他部分,如状态码、响应头等。
  7. 发送HTTP响应:完整的HTTP响应(包括HTML内容和响应头等)发送回客户端(浏览器)。
  8. 客户端渲染:浏览器接收到HTTP响应后,解析HTML内容并呈现给用户。

2.2 重要的点

  • 视图是后端的一部分:在这种架构中,视图(即视图模板)完全运行在服务器上,与传统意义上运行在浏览器中的“前端代码”不同。
  • 生成的HTML是通过HTTP响应传送的:最终生成的HTML页面是作为HTTP响应的一部分发送给客户端的,这个过程由 HttpServletResponse控制。
  • 服务器端渲染:这种模式被称为服务器端渲染(SSR),即页面的内容在服务器上生成,然后作为静态HTML发送到客户端。

这种设计模式使得服务器可以控制页面内容的生成,同时减少了客户端的计算负担,因为浏览器只负责显示最终的HTML内容。

返回值处理器

1. 实现方式

HandlerMethodReturnValueHandler返回值处理器顶层接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface HandlerMethodReturnValueHandler {

/**
* 检查该返回值处理器是否支持特定返回值
* @param returnType
* @return
*/
boolean supportsReturnType(MethodParameter returnType);

/**
* 处理控制器方法返回值的具体步骤
* @param returnValue 实际返回值
* @param returnType 返回值描述(类型、泛型信息、注解等)
* @param mavContainer 一个容器对象,用于在处理过程中存储模型数据或控制视图的渲染
* @param request HTTP请求对象
* @param response HTTP响应对象
* @throws Exception
*/
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer,
HttpServletRequest request, HttpServletResponse response) throws Exception;
}

五个常用的返回值处理器,支持Handler返回 Map ModleViewViewName以及被 @ResponseBody标注

主要处理 ModelAndView的Model部分

  1. ModelMethodReturnValueHandler,用于处理返回值类型为Model的控制器方法
  2. MapMethodReturnValueHandler,用于处理返回值类型为Map的控制器方法

主要处理 ModelAndView的View部分,分别返回视图名和视图对象

  • View视图对象接口
  1. ViewNameMethodReturnValueHandler,用于处理返回值类型为 CharSequence的控制器方法
  2. ``ViewMethodReturnValueHandler`,用于处理返回值类型为View的控制器方法

用于将方法的返回对象直接输出到HTTP响应中

  • @ResponseBody注解
  1. ResponseBodyMethodReturnValueHandler,用于处理被 @ResponseBody注解标注的控制器方法

五个返回值解析器的具体实现大同小异

  • 相同:都是实现返回值解析器接口,先检查是否为指定类型,再转换属性

  • 不同:

    • 前四个都是将获得的值添加到 ModelAndView容器当中,以便在视图渲染时使用,
    • @Response是直接将对象转换为JSON并直接写入响应体中,并标记本次请求已经处理完成,之后不再需要进一步的模型视图解析。

HandlerMethodReturnValueHandlerComposite返回值处理器聚合类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 存储多个返回值处理器
private List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();

// 遍历所有注册的返回值处理器,尝试找到一个支持当前返回值类型的处理器,并使用对应处理器处理返回值
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
HttpServletRequest request, HttpServletResponse response) throws Exception {
for (HandlerMethodReturnValueHandler handler : returnValueHandlers) {
if (handler.supportsReturnType(returnType)) {
handler.handleReturnValue(returnValue, returnType, mavContainer, request, response);
return;
}
}
throw new IllegalArgumentException("Unsupported parameter type [" +
returnType.getParameterType().getName() + "]. supportsParameter should be called first.");
}

2. 补充

CharSequence 是 Java 中的一个一个表示字符序列的接口,提供了访问字符序列的基本方法。这个接口是 Java 中所有字符序列类的超类,常见的实现类包括 StringStringBuilderStringBuffer。它提供了一些方法来访问和操作字符序列,但并没有规定具体的存储方式或实现细节,这些方法被它的所有实现类所实现。

常见实现类:

  • String:不可变的字符序列,一旦创建就不能修改。
  • StringBuilder:可变的字符序列,适用于需要频繁修改字符内容的情况。StringBuilder 是线程不安全的。
  • StringBuffer:可变的字符序列,与 StringBuilder 类似,但它是线程安全的,适用于多线程环境。

Handler执行器

1. InvocableHandlerMethod

InvocableHandlerMethod 提供了一个集成点,它将请求处理流程中的参数解析、方法调用和返回值处理结合起来,确保通过统一的方式进行处理。

<span style="color:red">可以说是一个包装器,”用于调用与 HTTP 请求相对应的方法,并处理方法的输入参数和返回值”将这些步骤包装起来,通过这个类的调用实现自动装配!

目前感觉类中的 invokeAndHandle()方法是最重要的,相当于 doCreateBean()refresh()方法的作用。

代码如下,核心类,结合注释好好看一遍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
public class InvocableHandlerMethod extends HandlerMethod {

// 参数名称发现器,用于发现方法参数的名称,有助于绑定请求参数到方法参数
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

// 参数解析器,解析方法参数
private HandlerMethodArgumentResolverComposite argumentResolver;

// 返回值解析器,处理方法返回值
private HandlerMethodReturnValueHandlerComposite returnValueHandler;

// 类型转换服务
private ConversionService conversionService;

// 构造函数
public InvocableHandlerMethod(HandlerMethod handlerMethod,
HandlerMethodArgumentResolverComposite argumentResolver,
HandlerMethodReturnValueHandlerComposite returnValueHandler,
ConversionService conversionService) {
super(handlerMethod);
this.argumentResolver = argumentResolver;
this.returnValueHandler = returnValueHandler;
this.conversionService = conversionService;
}

/**
* 调用处理器方法并处理其返回值
* @param request
* @param response
* @param mavContainer
* @throws Exception
*/
public void invokeAndHandle(HttpServletRequest request,
HttpServletResponse response,
ModelAndViewContainer mavContainer) throws Exception {

// 解析方法参数

// 1. 调用参数解析器解析方法参数,返回已经解析好的方法参数列表
List<Object> args = this.getMethodArgumentValues(request, response, mavContainer);

// 2. 通过反射执行目标控制器方法
Object resultValue = doInvoke(args);

// 如果返回值为null
if (Objects.isNull(resultValue)) {
// 如果响应已经提交,标记容器已处理完成,否则抛出异常
if (response.isCommitted()) {
mavContainer.setRequestHandled(true);
return;
} else {
throw new IllegalStateException("Controller handler return value is null");
}
}

// 如果返回值非null,则继续处理返回值
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandler != null, "No return value handler");

// 解析方法返回值

// 1. 创建MethodParameter实例(返回值信息(类型、泛型信息、注解等))
MethodParameter returnType = new MethodParameter(this.getMethod(), -1); //-1表示方法的返回值

// 2. 调用返回值处理器处理返回值
this.returnValueHandler.handleReturnValue(resultValue, returnType, mavContainer, request, response);

}

/**
* 通过反射直接执行对应的控制器方法
* getMethod() 获取要调用的方法
* getBean() 获取方法所在的实例
* args.toArray() 将参数列表转换为数组形式
* 返回值返回的就是控制器方法的返回值
* @param args
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
private Object doInvoke(List<Object> args) throws InvocationTargetException, IllegalAccessException {
return this.getMethod().invoke(this.getBean(), args.toArray());
}

/**
* 调用参数解析器解析方法参数,返回已经解析好的方法参数列表
* @param request
* @param response
* @param mavContainer
* @return
* @throws Exception
*/
private List<Object> getMethodArgumentValues(HttpServletRequest request,
HttpServletResponse response,
ModelAndViewContainer mavContainer) throws Exception {
Assert.notNull(argumentResolver, "HandlerMethodArgumentResolver can not null");

// 1. 获取方法参数的值列表
List<MethodParameter> parameters = this.getParameters();
List<Object> args = new ArrayList<>(parameters.size());

// 2. 遍历方法的参数列表,逐个调用参数解析器来解析参数,并将解析结果添加到参数值列表中
for (MethodParameter parameter : parameters) {
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args.add(argumentResolver.resolveArgument(parameter, request, response, mavContainer, conversionService));
}

// 3. 返回解析好的参数值列表
return args;
}

public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer;
}
}

2. ParameterNameDiscoverer

正确的参数名称识别对于确保数据正确绑定到方法调用的参数上是至关重要的。

指定参数的映射时,如果没有准确的参数的名称,开发者可能需要依赖于参数的顺序或使用额外的注解来指定参数的映射,这可能会导致代码更加复杂且容易出错。因此,ParameterNameDiscoverer 是支持高效和精确自动装配及数据绑定功能的关键组件之一。

由于Java的反射API不能直接获取到方法参数的名称,所以该类底层提供了一系列的方法来确定方法参数的名称。

2.1 底层实现

(复制粘贴的,还只能看懂个大概,暂时先了解一下吧)

Spring 提供了几个 ParameterNameDiscoverer 的实现,每种实现使用不同的策略来确定方法参数的名称:

  1. DefaultParameterNameDiscoverer: 这是一个复合实现,它按顺序尝试多个发现策略。默认情况下,它将尝试使用 StandardReflectionParameterNameDiscovererLocalVariableTableParameterNameDiscoverer
  2. StandardReflectionParameterNameDiscoverer: 使用 Java 8 引入的反射能力,只在编译时使用了 -parameters 选项时才能获取参数名称。
  3. LocalVariableTableParameterNameDiscoverer: 通过读取类的字节码中的局部变量表来发现参数名称。这需要方法在编译时包含调试信息(通常是使用 -g 选项)。
  4. AspectJAdviceParameterNameDiscoverer: 特别用于处理 AspectJ 的通知(advice)方法,可以解析通知方法的参数名称。

2.2 应用场景

尤其是在需要进行方法参数绑定的场景中

  • <span style="color:red">方法参数解析: 在 Spring MVC 中,控制器方法的参数需要从 HTTP 请求中解析并绑定,ParameterNameDiscoverer 能帮助解析器确定方法参数的具体名称,以便正确地从请求中提取相应的数据。

  • 数据绑定: 在需要将请求参数、路径变量或表单数据绑定到方法参数时,参数名称的正确识别是关键。

  • Spring AOP: 在创建通知(advice)时,参数名称可能会用于与切点表达式进行匹配。

    !!!AOP竟然也用到了,当时我是怎么实现的呢?

3. MethodParameter

还能与数据绑定有关,不过目前用到的是参数解析、类型转换的作用

3.1 表示方法参数信息

MethodParameter,通常用于表示方法参数的信息,提供了很多有用的方法来访问参数的元数据,例如参数类型、参数注解等

在参数解析器中扮演的作用

  • 先是判断支持的参数,用的是 parameter.hasParameterAnnotation(RequestBody.class)
  • 然后 JSON.parseObject将json转换为java对象,参数中用到了 MethodParameter获得类型
  • 获取注解实例用到了 parameter.getParameterAnnotation
  • 参数解析器new MethodParameter的时候
1
2
3
4
5
6
// HandlerMethod类 构造方法中
this.parameters = new ArrayList<>();
int parameterCount = method.getParameterCount();
for (int index = 0; index < parameterCount; index++) {
parameters.add(new MethodParameter(method, index));
}

将实例化 MethodParameter之前先复习一下 HandlerMethod

一个 HandlerMethod对象封装的是一个 处理程序方法的类 中 对应控制器方法 的信息

四个参数

  1. 控制器类bean
  2. bean类型
  3. 对应控制器方法对象
  4. 方法参数对象列表

然后index的作用的发挥出来了,用来标识方法参数的顺序,保证方法调用的时候参数按正确的顺序和类型被传递,确保精确度。

3.2 表示方法返回值类型信息

通过特殊的方法使用,MethodParameter也能代表某个方法的返回类型,包括方法返回值的类型、泛型信息、以及任何可能存在的注解等元数据

1
MethodParameter returnType = new MethodParameter(this.getMethod(), -1); 

关键在于第二个参数-1,它在 MethodParameter 的上下文中用来特别指示这个 MethodParameter 实例是用来表示方法的返回类型,而不是一个特定的方法参数。

在返回值解析器中扮演的作用

  • supportsReturnType方法检查类型的时候用到
  • 在抛异常的时候用到,打印了些参数
1
2
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
  • 当然,最重要的是new MethodParameter对象的时候
1
2
3
4
5
// InvocableHandlerMethod#invokeAndHandle
// 创建MethodParameter实例(返回值信息(类型、泛型信息、注解等))
MethodParameter returnType = new MethodParameter(this.getMethod(), -1);
// 调用返回值处理器处理返回值
this.returnValueHandler.handleReturnValue(resultValue, returnType, mavContainer, request, response);

处理器适配器

实现 RequestMappingHandlerAdapter

1. HandlerAdapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* <p>用于处理HTTP请求,并将请求映射到相应的处理器方法(即Controller方法)
* <p>主要作用是将处理器方法(HandlerMethod)与实际的HTTP请求和响应关联起来,返回一个模型和视图对象
* <p>充当HTTP请求和SpringMVC处理器方法之间的桥梁
*/
public interface HandlerAdapter {

/**
* 用于定义如何处理一个HTTP请求
* @param request HTTP请求
* @param response HTTP响应
* @param handler 处理当前请求的方法。它是一个封装了具体处理逻辑的方法,包含了该方法的所有相关信息,如方法参数、返回值类型、所属的Controller实例等。
* @return 返回ModelAndView容器对象
* @throws Exception
*/
ModelAndView handle(HttpServletRequest request, HttpServletResponse response,
HandlerMethod handler) throws Exception;
}

2. RequestMappingHandlerAdapter

最常用的处理器适配器,相比于集成点对象,这里是实例化集成点对象的地方,通过初始化方法,初始化了集成点所需的各个参数,并将返回值转换为标准的 ModelAndView对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* 多种处理器适配器中的一种,主要负责处理通过@RequestMapping注解定义的请求
*/
public class RequestMappingHandlerAdapter implements HandlerAdapter, InitializingBean {

// 自定义参数解析器列表
private List<HandlerMethodArgumentResolver> customArgumentResolvers;

// 内嵌的复合参数解析器
private HandlerMethodArgumentResolverComposite argumentResolverComposite;

// 自定义返回值处理器列表
private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;

// 内嵌的复合返回值处理器
private HandlerMethodReturnValueHandlerComposite returnValueHandlerComposite;

// 类型转换服务
private ConversionService conversionService;

/**
* 定义了如何处理一个HTTP请求
*/
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response,
HandlerMethod handlerMethod) throws Exception {

// 1. 创建了一个集成点对象,里面的成员变量在初始化过程中被赋值
InvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);

// 2. 创建了一个ModelAndViewContainer对象
ModelAndViewContainer mavContainer = new ModelAndViewContainer();

// 3. 调用invokeAndHandle方法,执行Controller方法,并将结果传递给ModelAndViewContainer对象
invocableMethod.invokeAndHandle(request, response, mavContainer);

// 4. 根据返回的ModelAndViewContainer对象中的值,返回ModelAndView对象
return getModelAndView(mavContainer);
}

/**
* 在属性设置完成后被调用,用于执行一些初始化操作
* 初始化了参数解析器和返回值处理器
*/
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(conversionService, "conversionService can not null");

if (Objects.isNull(argumentResolverComposite)) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolverComposite = new HandlerMethodArgumentResolverComposite();
this.argumentResolverComposite.addResolver(resolvers);
}

if (Objects.isNull(returnValueHandlerComposite)) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlerComposite = new HandlerMethodReturnValueHandlerComposite();
this.returnValueHandlerComposite.addReturnValueHandler(handlers);
}
}

// 剩下都是些填充成员变量的方法,还有setter、getter
}

3. ModelAndView&ModelAndViewContainer

ModelAndViewModelAndViewContainer的区别与关系

3.1 ModelAndView

主要用于封装模型数据和视图信息,承载了模型和视图的信息,以便后续渲染

3.2 ModelAndViewContainer

主要用于在请求处理的过程中管理模型数据视图渲染的决策

管理模型数据:就是封装模型数据和视图信息,和 ModelAndView一样

视图渲染的决策:例如管理请求处理状态,是否需要进一步渲染视图,主要在框架的内部使用,用于更好的自动管理模型数据和视图信息。

视图的渲染

主要涉及两种,重定向视图的渲染和JSP视图的渲染

白雪,已经淘汰啦!后端现在根本不用管视图怎么渲染,什么视图解析器!给前端返回数据就行了!就当是体会了一下技术的迭代吧。

1. RedirectView

Spring MVC集成servlet后端重定向的方法。

不过在前后端分离的开发模式中,这种方式已经不是很常用了(虽然能用,重定向是可以重定向到项目外部资源的)

原因:随着单页面应用(SPA)和现代JavaScript框架(如React, Vue, Angular)的流行,前端更多地采用AJAX和API调用来管理数据和路由,而避免全页面的重载。在这种模式下,重定向通常是通过前端路由来处理,而非由后端HTTP重定向完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* 重定向视图
*/
public class RedirectView extends AbstractView {
private String url;

public RedirectView(String url) {
this.url = url;
}

@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
String targetUrl = createTargetUrl(model, request);
response.sendRedirect(targetUrl);
}

/**
* model中的数据添加到URL后面作为参数
* 按照HTTP规范,当服务器返回一个重定向响应后,客户端应该使用GET方法去请求在Location头部指定的URL,而不是继续使用POST方法。所以这里使用的是get方式拼接路径。
*
* @param model
* @param request
* @return
*/
private String createTargetUrl(Map<String, Object> model, HttpServletRequest request) {
Assert.notNull(this.url, "url can not null");

StringBuilder queryParams = new StringBuilder();
model.forEach((key, value) -> {
queryParams.append(key).append("=").append(value).append("&");
});
if (queryParams.length() > 0) {
queryParams.deleteCharAt(queryParams.length() - 1);
}
StringBuilder targetUrl = new StringBuilder();
if (this.url.startsWith("/")) {
// Do not apply context path to relative URLs.
targetUrl.append(getContextPath(request));
}

targetUrl.append(url);

if (queryParams.length() > 0) {
targetUrl.append("?").append(queryParams.toString());
}
return targetUrl.toString();
}

private String getContextPath(HttpServletRequest request) {
String contextPath = request.getContextPath();
while (contextPath.startsWith("//")) {
contextPath = contextPath.substring(1);
}
return contextPath;
}

public String getUrl() {
return url;
}
}

2. InternalResourceView

这属于传统的基于服务器的页面渲染方式了,在前后端分离的方式中已经被淘汰。

前后端分离通常意味着前端(如使用React, Angular, Vue等构建的应用)独立于后端,通过API(通常是REST API)与后端进行交互。前端负责所有的用户界面逻辑和展示,而后端则专注于数据处理和业务逻辑。在这种模式下,后端主要返回JSON或XML等格式的数据,而不是生成HTML页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* Spring MVC框架中用于处理服务器内部资源(如JSP页面)转发的视图
* <p>总的来说,InternalResourceView 类提供了一种将控制器逻辑与 JSP 或其他服务器资源的视图层分离的方法,通过将模型数据暴露为请求属性,并将请求转发到服务器内部的具体资源。这种方式非常适用于传统的基于服务器的页面渲染方法,现已几乎被淘汰。</p>
*/
public class InternalResourceView extends AbstractView {
private String url;

public InternalResourceView(String url) {
this.url = url;
}

@Override
public String getContentType() {
return "text/html";
}

@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

// 将模型数据暴露给JSP或其他服务端资源
exposeModelAsRequestAttributes(model, request);

//
RequestDispatcher rd = request.getRequestDispatcher(this.url);
rd.forward(request, response);
}

/**
* 把model中的数据放入到request
*
* @param model
* @param request
*/
private void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) {
model.forEach((name, value) -> {
if (Objects.nonNull(value)) {
request.setAttribute(name, value);
} else {
request.removeAttribute(name);
}
});
}

public String getUrl() {
return url;
}
}

视图解析器

image

1. ViewResolver接口

1
2
3
public interface ViewResolver {
View resolveViewName(String viewName) throws Exception;
}

2. AbstractCachingViewResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* 用于解析视图名称到具体的View实例的抽象类
* <p>引入了缓存机制来提高视图解析的效率
*/
public abstract class AbstractCachingViewResolver implements ViewResolver {

// 一个私有的锁对象,用于同步代码块,确保线程安全
private final Object lock = new Object();

// 静态常量,代表无法解析的视图
private static final View UNRESOLVED_VIEW = (model, request, response) -> {
};

// 缓存,用于存储已解析的视图名称和对应的View实例
private Map<String, View> cachedViews = new HashMap<>();

/**
* 解析ViewName为View对象
* @param viewName
* @return
* @throws Exception
*/
@Override
public View resolveViewName(String viewName) throws Exception {
View view = cachedViews.get(viewName);
if (Objects.nonNull(view)) {
return (view != UNRESOLVED_VIEW ? view : null);
}

synchronized (lock) {
// 再次检查缓存(以防在等待锁的时间内其他线程已经缓存了视图)
view = cachedViews.get(viewName);
if (Objects.nonNull(view)) {
return (view != UNRESOLVED_VIEW ? view : null);
}

// 创建新的视图
view = createView(viewName);

// 如果返回null,代表视图无法解析,将UNRESOLVED_VIEW存入缓存
if (Objects.isNull(view)) {
view = UNRESOLVED_VIEW;
}
cachedViews.put(viewName, view);
}
return (view != UNRESOLVED_VIEW ? view : null);
}

/**
* 根据视图名称创建View对象,负责具体的视图解析逻辑
* @param viewName
* @return
*/
protected abstract View createView(String viewName);
}

3. UrlBasedViewResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package org.springframework.web.view.resolver;

import org.springframework.web.view.InternalResourceView;
import org.springframework.web.view.RedirectView;
import org.springframework.web.view.View;

/**
* 用于解析基于URL的视图,包括重定向和转发操作
*/
public abstract class UrlBasedViewResolver extends AbstractCachingViewResolver {

// 静态变量,用于定义视图名称的前缀
public static final String REDIRECT_URL_PREFIX = "redirect:";
public static final String FORWARD_URL_PREFIX = "forward:";

// 视图名称的前后缀,用于构建最终的视图URL
private String prefix = "";
private String suffix = "";

/**
* 用于根据ViewName创建具体的View实例
* @param viewName
* @return
*/
@Override
protected View createView(String viewName) {

// 先检查视图名称是否以特定的前缀开始,以确定是否需要执行重定向或转发

// 如果是重定向,创建RedirectView
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
return new RedirectView(redirectUrl);
}

// 如果是转发,创建InternalResourceView
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}

// 如果都不是,调用buildView方法解析视图
return buildView(viewName);
}

protected abstract View buildView(String viewName);

public String getPrefix() {
return prefix;
}

public void setPrefix(String prefix) {
this.prefix = prefix;
}

public String getSuffix() {
return suffix;
}

public void setSuffix(String suffix) {
this.suffix = suffix;
}
}

4. InternalResourceViewResolver

1
2
3
4
5
6
7
8
9
10
/**
* 服务器内部资源视图(如JSP、HTML等)解析器
*/
public class InternalResourceViewResolver extends UrlBasedViewResolver {
@Override
protected View buildView(String viewName) {
String url = getPrefix() + viewName + getSuffix();
return new InternalResourceView(url);
}
}

5. 拼接前后缀

解析 ViewName主要分为两类

  1. 请求转发和响应重定向
  2. 服务器内部资源视图解析器(解析服务器内部的JSP、HTML等)

UrlBasedViewResolver里定义了前缀后缀两个成员变量,请求转发和响应重定向是用不到的,解析内部视图的时候用到,如下:

这种设计使得 InternalResourceViewResolver 成为处理基于服务器的视图(如 JSP 或其他模板引擎页面)的理想选择。通过配置前缀和后缀,可以非常灵活地控制资源路径的解析,适应不同的部署环境或应用需求。例如,可以将前缀设置为 “/WEB-INF/views/“,后缀设置为 “.jsp”,从而使得 viewName “home” 解析为 “/WEB-INF/views/home.jsp”,这对于在 MVC 应用中定位视图文件非常有用。

InternalResourceViewResolver 提供了一种简单有效的方法来解析并转发到应用内部定义的资源,使得视图管理在 Spring MVC 应用中变得既清晰又高效。

6. RequestContextHolder工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 利用ThreadLocal机制,在当前线程中存储和访问HttpServletRequest对象
*/
public abstract class RequestContextHolder {

// 存储HttpServletRequest对象
private static final ThreadLocal<HttpServletRequest> inheritableRequestHolder =
new NamedInheritableThreadLocal<>("Request context");

// 重置线程中的HttpServletRequest对象
public static void resetRequest() {
inheritableRequestHolder.remove();
}

public static void setRequest(HttpServletRequest request) {
inheritableRequestHolder.set(request);
}

public static HttpServletRequest getRequest() {
return inheritableRequestHolder.get();
}
}

7. ContentNegotiatingViewResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
* 一个集成了所有配置视图解析器的复合类,它通过内部的策略(基于内容协商)来选择最合适的视图解析器进行视图解析
* <p>本身充当管理者的角色,调度和协调其他视图解析器来根据请求的内容类型选择最合适的视图。
*/
public class ContentNegotiatingViewResolver implements ViewResolver, InitializingBean {

// 一个包含多个 ViewResolver 的列表,用于解析视图名称到具体的 View 实例
private List<ViewResolver> viewResolvers = new ArrayList<>();

// 一个包含默认 View 实例的列表,这些视图可以用于在没有特定匹配时提供备选视图
private List<View> defaultViews = new ArrayList<>();

@Override
public View resolveViewName(String viewName) throws Exception {

// 获取所有候选视图
List<View> candidateViews = getCandidateViews(viewName);

// 选择最佳视图
View bestView = getBestView(candidateViews);
if(Objects.nonNull(bestView)){
return bestView;
}

// 如果没有合适的视图,返回null
return null;
}

/**
* 根据请求找出最优视图
*
* @param candidateViews
* @return
*/
private View getBestView(List<View> candidateViews) {

// 从候选视图中查找是否存在RedirectView类型的视图
Optional<View> viewOptional = candidateViews.stream()
.filter(view -> view instanceof RedirectView)
.findAny();

// 如果RedirectView类型的视图存在,则优先返回该视图
if (viewOptional.isPresent()) {
return viewOptional.get();
}

// 从RequestContextHolder中获取当前请求
HttpServletRequest request = RequestContextHolder.getRequest();

// 获取请求中的Accept头部,Accept头部说明了客户端期望接收哪种内容类型
Enumeration<String> acceptHeaders = request.getHeaders("Accept");

// 循环遍历Accept头部和候选视图,查找内容类型匹配的视图
while (acceptHeaders.hasMoreElements()) {
for (View view : candidateViews) {
if (acceptHeaders.nextElement().contains(view.getContentType())) {
return view;
}
}
}
return null;
}

/**
* 先找出所有候选视图
*
* @param viewName
* @return
* @throws Exception
*/
private List<View> getCandidateViews(String viewName) throws Exception {

// 初始化候选视图列表
List<View> candidateViews = new ArrayList<>();

// 遍历所有视图解析器尝试解析视图
for (ViewResolver viewResolver : viewResolvers) {
// 如果解析到了,那就将解析好的View对象存入视图列表当中
View view = viewResolver.resolveViewName(viewName);
if (Objects.nonNull(view)) {
candidateViews.add(view);
}
}

// 如果有默认视图并且视图列表不为空,则将默认视图也加入到候选视图列表中
if (!CollectionUtils.isEmpty(defaultViews)) {
candidateViews.addAll(defaultViews);
}
return candidateViews;
}

/**
* 检查ViewResolvers是否被正确设置
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(viewResolvers, "viewResolvers can not null");
}

/**
* 设置视图解析器
* @param viewResolvers
*/
public void setViewResolvers(List<ViewResolver> viewResolvers) {
this.viewResolvers = viewResolvers;
}

/**
* 设置默认视图
* @param defaultViews
*/
public void setDefaultViews(List<View> defaultViews) {
this.defaultViews = defaultViews;
}
}

DispatcherServlet

image

dispatcher:调度

核心类,充当前端控制器的角色,负责处理所有通过Spring应用的HTTP请求。

<span style="color:red">主要职责是协调各种资源,完成对HTTP请求的调度分发,包括控制器(controllers)、视图解析器(view resolvers)、处理器映射(handler mappings)等,<span style="color:red">来处理请求和生成响应

extends HttpServlet 使其可以处理 HTTP 请求和响应

implements ApplicationContextAware 允许它接入 Spring 容器,管理 Spring Bean

主要分为两部分

  • 一部分是借助 GenericServletinit初始化方法和对 ApplicationContext的感知,完成自身属性的初始化

  • 另一部分就是协调各种资源,完成对HTTP请求的调度分发,处理请求、生成响应。

    简要概述其工作流程如下:

    1. 请求接收DispatcherServlet 接收到客户端发送的 HTTP 请求。
    2. 请求解析:根据请求的 URL、HTTP 方法等信息,DispatcherServlet 使用合适的 HandlerMapping 查找并选择对应的处理器(Controller中的方法)。
    3. 请求委托:一旦确定了处理器,DispatcherServlet 会调用相应的 HandlerAdapter 来执行处理器方法。
    4. 模型与视图:处理器方法执行完成后,通常会返回一个 ModelAndView 对象,其中包含了模型数据和视图名称。
    5. 视图渲染:根据返回的视图名称,DispatcherServlet 使用配置的 ViewResolver 来解析视图。解析后的 View 对象负责渲染模型数据,生成最终的页面。
    6. 响应返回:渲染完的页面作为 HTTP 响应返回给客户端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
public class DispatcherServlet extends HttpServlet implements ApplicationContextAware {

// 初始化日志记录器
private Logger logger = LoggerFactory.getLogger(getClass());

// 引用Spring应用上下文
private ApplicationContext applicationContext;

// Spring MVC 处理请求的核心组件

// 处理请求的映射处理器。确定有那个控制器处理传入的请求
private HandlerMapping handlerMapping;

// 用于执行处理器逻辑的适配器。调用处理器方法的适配器
private HandlerAdapter handlerAdapter;

// 用于解析视图名称到具体试图实现的解析器。解析视图名到具体的视图实现
private ViewResolver viewResolver;

// 异常处理器集合。处理请求过程中出现的异常的解析器集合
private Collection<HandlerExceptionResolver> handlerExceptionResolvers;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

/**
* 继承自GenericServlet的init初始化方法,从Spring上下文中获取必要的组件,
* 如处理器映射、处理器适配器、视图解析器和异常处理器集合,
* 即HandlerMapping, HandlerAdapter, ViewResolver 和 HandlerExceptionResolver 的实例。
*/
@Override
public void init() {
this.handlerMapping = this.applicationContext.getBean(HandlerMapping.class);
this.handlerAdapter = this.applicationContext.getBean(HandlerAdapter.class);
this.viewResolver = this.applicationContext.getBean(ViewResolver.class);
this.handlerExceptionResolvers =
this.applicationContext.getBeansOfType(HandlerExceptionResolver.class).values();
}

/**
* 用于接收HTTP请求并调用doDispatch方法来分发请求
* @param request the {@link HttpServletRequest} object that
* contains the request the client made of
* the servlet
*
* @param response the {@link HttpServletResponse} object that
* contains the response the servlet returns
* to the client
*
* @throws ServletException
* @throws IOException
*/
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
logger.info("DispatcherServlet.service => uri:{}", request.getRequestURI());

// 设置当前线程的请求上下文,使得在应用的任何地方都可以访问到当前的请求和响应对象
RequestContextHolder.setRequest(request);

try {
// 调用doDispatch方法来分发请求
doDispatch(request, response);
} catch (Exception e) {
logger.error("Handler the request fail", e);
} finally {
// 请求结束后重置请求上下文,清理与当前线程关联的请求和响应对象
RequestContextHolder.resetRequest();
}

}

/**
* 分发请求
* @param request
* @param response
* @throws Exception
*/
private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

// 用于记录过程中发生的任何异常
Exception dispatchException = null;

// 用于保存处理请求的处理器链
HandlerExecutionChain executionChain = null;
try {
ModelAndView mv = null;
try {
// 获取与请求匹配的处理器链
executionChain = this.handlerMapping.getHandler(request);

// 执行处理器链中的预处理操作。如果预处理返回false,则直接返回不继续执行后续操作
if (!executionChain.applyPreHandle(request, response)) {
return;
}

// 实际调用处理器,实例化集成点对象的地方,实际处理了一个HTTP请求,返回ModelAndView对象
// (这里集成了之前所有的参数解析器,返回值处理器,Handler执行器,集成点对象(从拦截器链中获取的))
mv = handlerAdapter.handle(request, response, executionChain.getHandler());

// 执行拦截器链后处理操作
executionChain.applyPostHandle(request, response, mv);
} catch (Exception e) {
/**
* 如果内部处理器不能解决异常,或者由于某种原因重新抛出了异常,
* 那么这个异常就会被外部 try-catch 块捕获并再次抛出,
* 此时它将逃离 Spring 的处理流程,交由更上层的异常处理机制处理
*/
dispatchException = e;
}
// 处理分发结果,包括视图渲染和异常处理
processDispatchResult(request, response, mv, dispatchException);
} catch (Exception ex) {
//
dispatchException = ex;
throw ex;
} finally {
// 执行处理链的完成后处理方法,无论请求处理过程是否成功
if (Objects.nonNull(executionChain)) {
executionChain.triggerAfterCompletion(request, response, dispatchException);
}
}
}

/**
* 处理分发结果,包括视图渲染和异常处理
* @param request
* @param response
* @param mv
* @param ex
* @throws Exception
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
ModelAndView mv, Exception ex) throws Exception {

// 检查是否有异常发生,如果有,调用processHandlerException方法处理异常,该方法响应错误json数据或者返回一个错误页面的ModelAndView
if (Objects.nonNull(ex)) {
//error ModelAndView
mv = processHandlerException(request, response, ex);
}

// 检查ModelAndView是否为null
if (Objects.nonNull(mv)) {
// 如果不为null,则调用render方法渲染视图
render(mv, request, response);
return;
}
// 如果为null,记录日志,无视图被渲染
logger.info("No view rendering, null ModelAndView returned.");
}

/**
* 渲染视图
* @param mv
* @param request
* @param response
* @throws Exception
*/
private void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
throws Exception {

View view;
String viewName = mv.getViewName();
if (!StringUtils.isEmpty(viewName)) {
view = this.viewResolver.resolveViewName(viewName);
} else {
view = (View) mv.getView();
}

if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().getValue());
}
view.render(mv.getModel().asMap(), request, response);
}

// 出现异常后的ModelAndView
private ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Exception ex) throws Exception {
if (CollectionUtils.isEmpty(this.handlerExceptionResolvers)) {
throw ex;
}
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
ModelAndView exMv = resolver.resolveException(request, response, ex);
if (exMv != null) {
return exMv;
}
}
//未找到对应的异常处理器,就继续抛出异常
throw ex;
}
}

全局异常处理器

<span style="color:red">终于看懂了,熟悉使用是看源码的前提!如果连它的作用都不知道是什么,那源码肯定看不懂!

image

先举一个全局异常处理器实际使用的例子,看源码时要集合这个例子想象Spring MVC底层是如何识别各个注解,如何根据异常信息自动识别执行对应异常处理方法,如何将异常处理方法当作正常的控制器方法执行的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResultInfo runtimeHandleException(RuntimeException e){
//模拟一个ResultInfo
ResultInfo resultInfo = new ResultInfo(0,"RuntimeException",null);
return resultInfo;
}
@ExceptionHandler(IOException.class)
public ResultInfo ioHandleException(IOException e){
//模拟一个ResultInfo
ResultInfo resultInfo = new ResultInfo(0,"IOException",null);
return resultInfo;
}
}

1. 注解

@ControllerAdvice

可以使用 @ControllerAdvice来声明一个类来统一对所有 @RequestMapping方法来做 @ExceptionHandler@InitBinder以及 @ModelAttribute处理。

1
2
3
4
5
6
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
}

@ExceptionHandler

使用全局异常处理器的方法上,声明对 Exception的处理逻辑。可以指定目标 Exception

1
2
3
4
5
6
7
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {

Class<? extends Throwable>[] value() default {};
}

2. ExceptionHandlerMethodResolver

ExceptionHandlerMethodResolver的作用是封装全局异常处理器中定义的方法,建立异常类型与处理方法之间的映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
* 主要作用:解析带有@ExceptionHandler注解的处理异常的方法,并建立异常类型与处理方法之间的映射(就是封装全局异常处理器中定义的方法呗)
*/
public class ExceptionHandlerMethodResolver {

// exception_handler_methods,MethodFilter实例,表示一个过滤条件(此处定义的条件是方法上是否存在@ExceptionHandler注解)
/*public static final ReflectionUtils.MethodFilter EXCEPTION_HANDLER_METHODS = method ->
AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);*/
public static final ReflectionUtils.MethodFilter EXCEPTION_HANDLER_METHODS = new ReflectionUtils.MethodFilter() {
@Override
public boolean matches(Method method) {
return AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
}
};

// 用于存储异常类和处理这些异常的方法之间的映射关系(ConcurrentReferenceHashMap线程安全,允许高效的并发访问)
private final Map<Class<? extends Throwable>, Method> mappedMethods = new ConcurrentReferenceHashMap<>(16);

/**
* 构造函数
* @param handlerType
*/
public ExceptionHandlerMethodResolver(Class<?> handlerType) {

// MethodIntrospector.selectMethods:筛选出handlerType处理器类中所有被@ExceptionHandler注解标记的方法,EXCEPTION_HANDLER_METHODS是一个过滤条件,充当参数
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
// 对于找到的每一个方法,进一步调用detectExceptionMappings来确定此方法可以处理哪些类型的异常(就是获取注解上指定的异常信息,例如@ExceptionHandler(RuntimeException.class))
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
// 将每个异常类型和对应的处理方法对象添加到mappedMethods映射表中
this.mappedMethods.put(exceptionType, method);
}
}
}

/**
* 获取注解上指定的异常信息
* @param method
* @return
*/
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
// 查找method上的@ExceptionHandler注解
ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);
// 如果没有@ExceptionHandler注解,抛出异常
Assert.state(ann != null, "No ExceptionHandler annotation");
// 返回注解中指定的异常类型数组
return Arrays.asList(ann.value());
}

public Map<Class<? extends Throwable>, Method> getMappedMethods() {
return mappedMethods;
}

public boolean hasExceptionMappings() {
return !this.mappedMethods.isEmpty();
}

/**
* 返回处理异常的方法
* @param exception
* @return
*/
public Method resolveMethod(Exception exception) {
// 首先尝试直接根据异常的类型找到处理方法
Method method = resolveMethodByExceptionType(exception.getClass());
// 如果没有找到处理方法(method == null),则检查异常是否有原因异常(cause)
if (method == null) {
Throwable cause = exception.getCause();
// 如果有,尝试根据原因异常的类型找到处理方法
if (cause != null) {
method = resolveMethodByExceptionType(cause.getClass());
}
}
// 返回找到的方法或null
return method;
}

/**
* 通过异常类型在映射表中检索对应的处理方法
* @param exceptionClass
* @return
*/
private Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionClass) {
return mappedMethods.get(exceptionClass);
}
}

2.1 涉及的两个工具类

MethodIntrospector.selectMethods()

两个参数:

  1. Class<?> 要进行方法选择的目标类
  2. ReflectionUtils.MethodFilter 方法过滤器,用于定义哪些方法符合选择的条件

返回值:符合条件的方法对象列表

(又是两个工具类)

3. ControllerAdviceBean

包装类,用于封装和管理带有 @ControllerAdvice注解的 bean

核心只有这个方法,返回所有 ControllerAdviceBean实例的列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 获取所有被@ControllerAdvice注解标注的bean,以集合的形式,ControllerAdviceBean即本类类型
* @param context 应用上下文
* @return
*/
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
// 通过工具方法获取应用上下文及其所有bean
Map<String, Object> beanMap = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, Object.class);
return beanMap.entrySet().stream()
// 过滤处具有@ControllerAdvice注解的bean
.filter(entry -> hasControllerAdvice(entry.getValue()))
// 将符合条件的bean封装进ControllerAdviceBean实例中
.map(entry -> new ControllerAdviceBean(entry.getKey(), entry.getValue()))
// 返回一个包含所有符合条件的ControllerAdviceBean实例的列表
.collect(toList());
}

4. ExceptionHandlerExceptionResolver

全局异常处理的核心类

主要作用是解析和管理所有全局异常处理器中的异常处理方法,将异常处理方法融入Spring MVC对控制器方法的处理当中。

主要分为两部分:

一部分是对该核心类的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void afterPropertiesSet() throws Exception {
// 检查类型转换服务已被设置
Assert.notNull(this.conversionService, "conversionService can not null");
// 初始化异常处理器缓存
initExceptionHandlerAdviceCache();
// 设置参数解析器和返回值处理器
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite();
this.argumentResolvers.addResolver(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
this.returnValueHandlers.addReturnValueHandler(handlers);
}
}

另一部分是解析和管理异常处理方法,将其融入Spring MVC对控制器方法的处理当中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Exception ex) {
// 通过getExceptionHandlerMethod方法获取封装了处理该异常方法的集成点对象
InvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(ex);
if (exceptionHandlerMethod == null) {
return null;
}
// 存储模型和视图信息
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
// 将处理异常的方法移交给集成点对象的invokeAndHandle执行
try {
Throwable cause = ex.getCause();
if (Objects.nonNull(cause)) {
exceptionHandlerMethod.invokeAndHandle(request, response, mavContainer, cause);
} else {
exceptionHandlerMethod.invokeAndHandle(request, response, mavContainer, ex);
}
} catch (Exception e) {
logger.error("exceptionHandlerMethod.invokeAndHandle fail", e);
return null;
}
// 检查请求是否已被处理(是否要生成视图)
if (mavContainer.isRequestHandled()) {
return null;
}
// 如果未被处理的话,返回ModelAndView对象
ModelAndView mav = new ModelAndView();
mav.setStatus(mavContainer.getStatus());
mav.setModel(mavContainer.getModel());
mav.setView(mavContainer.getView());
return mav;
}

5. InvocableHandlerMethod参数处理

之前的 invokeAndHandle方法并未考虑手动传参的情况,参数解析器会无法处理异常处理方法的参数,现在加上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private List<Object> getMethodArgumentValues(HttpServletRequest request,
HttpServletResponse response,
ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Assert.notNull(argumentResolver, "HandlerMethodArgumentResolver can not null");
// 1. 获取方法参数的值列表
List<MethodParameter> parameters = this.getParameters();
List<Object> args = new ArrayList<>(parameters.size());
// 2. 遍历方法的参数列表,逐个调用参数解析器来解析参数,并将解析结果添加到参数值列表中
for (MethodParameter parameter : parameters) {
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
/////////////////////////
Object arg = findProvidedArgument(parameter, providedArgs);
if (Objects.nonNull(arg)) {
args.add(arg);
continue;
}
/////////////////////////
args.add(argumentResolver.resolveArgument(parameter, request, response, mavContainer, conversionService));
}
// 3. 返回解析好的参数值列表
return args;
}
/////////////////////////
protected static Object findProvidedArgument(MethodParameter parameter, Object... providedArgs) {
if (!ObjectUtils.isEmpty(providedArgs)) {
for (Object providedArg : providedArgs) {
if (parameter.getParameterType().isInstance(providedArg)) {
return providedArg;
}
}
}
return null;
}
/////////////////////////

6. 融入DispatcherServlet

  1. 定义变量
1
private Collection<HandlerExceptionResolver> handlerExceptionResolvers;
  1. init中完成初始化
1
2
this.handlerExceptionResolvers =
this.applicationContext.getBeansOfType(HandlerExceptionResolver.class).values();
  1. 完成 processHandlerException中的处理逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Exception ex) throws Exception {
if (CollectionUtils.isEmpty(this.handlerExceptionResolvers)) {
throw ex;
}
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
ModelAndView exMv = resolver.resolveException(request, response, ex);
if (exMv != null) {
return exMv;
}
}
//未找到对应的异常处理器,就继续抛出异常
throw ex;
}
  1. processDispatchResult处理分发结果方法中,加上对异常的判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
ModelAndView mv, Exception ex) throws Exception {

/////////////////////////////
// 检查是否有异常发生,如果有,调用processHandlerException方法处理异常,该方法响应错误json数据或者返回一个错误页面的ModelAndView
if (Objects.nonNull(ex)) {
//error ModelAndView
mv = processHandlerException(request, response, ex);
}
/////////////////////////////

// 检查ModelAndView是否为null
if (Objects.nonNull(mv)) {
// 如果不为null,则调用render方法渲染视图
render(mv, request, response);
return;
}

// 如果为null,记录日志,无视图被渲染
logger.info("No view rendering, null ModelAndView returned.");
}

无论控制器方法返回的是响应数据还是视图,该方法最终都会被执行到,然后进行统一的异常处理。

  1. doDispatch()方法中的两层 try-catch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 用于记录过程中发生的任何异常
Exception dispatchException = null;
// 用于保存处理请求的处理器链
HandlerExecutionChain executionChain = null;
try {
ModelAndView mv = null;
try {
// 获取与请求匹配的处理器链
executionChain = this.handlerMapping.getHandler(request);
// 执行处理器链中的预处理操作。如果预处理返回false,则直接返回不继续执行后续操作
if (!executionChain.applyPreHandle(request, response)) {
return;
}
// 实际调用处理器,实例化集成点对象的地方,实际处理了一个HTTP请求,返回ModelAndView对象
// (这里集成了之前所有的参数解析器,返回值处理器,Handler执行器,集成点对象(从拦截器链中获取的))
mv = handlerAdapter.handle(request, response, executionChain.getHandler());
// 执行拦截器链后处理操作
executionChain.applyPostHandle(request, response, mv);
} catch (Exception e) {
/**
* 如果内部处理器不能解决异常,或者由于某种原因重新抛出了异常,
* 那么这个异常就会被外部 try-catch 块捕获并再次抛出,
* 此时它将逃离 Spring 的处理流程,交由更上层的异常处理机制处理
*/
dispatchException = e;
}
// 处理分发结果,包括视图渲染和异常处理
processDispatchResult(request, response, mv, dispatchException);
} catch (Exception ex) {
//
dispatchException = ex;
throw ex;
} finally {
// 执行处理链的完成后处理方法,无论请求处理过程是否成功
if (Objects.nonNull(executionChain)) {
executionChain.triggerAfterCompletion(request, response, dispatchException);
}
}
}

外部的 try-catch确保了异常管理的健壮性和灵活性,不仅保证了在Spring MVC内部有充分的机会处理异常,也保证了异常不会丢失,继续向上抛出,传递到更广泛的上下文中去处理。

如果没有外部的 try-catch

  • 一方面会导致异常丢失,导致异常信息不完整,使得开发者难以理解和追踪错误发生的原因和位置
  • 另一方面,对于用户体验来说,用户无法看到更准确的错误信息,影响体验

7. 异常链

7.1 概述

异常链,用来描述一个异常是如何由另一个异常引起的。在处理复杂系统或多层架构的错误时,了解错误发生的完整路径和原因非常关键。异常链允许开发者将一个异常封装到另一个异常中,从而保持关于错误原因的详细信息不被丢失,同时也能添加更多的上下文信息或者将异常转换为更适合当前层级处理的形式。

7.2 作用与目的

异常链的主要目的是保留错误处理过程中的完整信息,使得调试和错误诊断更加高效。这样做的好处包括:

  1. 保持错误上下文:在将异常从一个系统层次传递到另一个系统层次时,保持原始异常的信息,如具体的错误代码、失败的操作和其他诊断信息。
  2. 帮助诊断问题:底层异常常常包含了引起错误的直接原因,而顶层异常可能只说明了错误的一般性质或上下文。所以需要在异常从一个系统层次传递到另一个系统层次时,保持原始异常的信息,允许开发者或最终用户获得更详细的错误报告,包含原始错误的完整链条,而不仅仅是最后一环。

7.3 实现

1
2
3
4
5
try {
// 尝试执行某些可能失败的操作
} catch (LowLevelException e) {
throw new HighLevelException("Failed due to underlying error", e);
}

就是这么通过在异常的构造器中传递另一个异常对象来实现的。

7.4 检索异常链

调用异常对象的 getCause()方法可以访问这个链中的前一个异常,可以递归地完成,直到到达链中的第一个异常。

1
2
3
4
5
6
7
8
9
try {
// 高级别的操作,可能抛出封装了其他异常的高级别异常
} catch (HighLevelException e) {
Throwable cause = e.getCause();
while (cause != null) {
System.out.println(cause.getMessage());
cause = cause.getCause();
}
}

7.5 原因异常

原因异常(cause exception)通常是指在异常链中引发当前异常的上一个异常。

核心配置类

1. 组成组件

WebMvcConfigurer接口

定义了一系列用来自定义和配置Spring MVC的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface WebMvcConfigurer {
// 参数解析器的扩展点
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
}

// 返回值解析器
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
}

// 拦截器暴露对外的扩展点
default void addInterceptors(InterceptorRegistry registry) {
}

// 数据转换格式化暴露对外的扩展点
default void addFormatters(FormatterRegistry registry) {
}

// 视图的扩展点
default void addDefaultViews(List<View> views) {
}

// 视图解析器的扩展点
default void addViewResolvers(List<ViewResolver> viewResolvers) {
}
}

WebMvcConfigurerComposite

实现了 WebMvcConfigurer接口,集中管理多个 WebMvcConfigurer配置实例,使其可以整合在一个单一的组合体上进行配置,保持了代码的简洁和模块化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class WebMvcConfigurerComposite implements WebMvcConfigurer {

// 存储所有配置器对象
private List<WebMvcConfigurer> delegates = new ArrayList<>();

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
delegates.forEach(configurer -> configurer.addArgumentResolvers(argumentResolvers));
/*for (WebMvcConfigurer configurer : delegates) {
configurer.addArgumentResolvers(argumentResolvers);
}*/
}

//...
}

WebMvcConfigurationSupport

集中配置了Spring MVC所需的组件,将其注入Spring容器当中,从而提供了 DispatchServlet所有需要使用到的组件,并提供了一系列钩子,允许开发者自定义和扩展Spring MVC的行为。

提供的组件:

  1. 构建数据转换服务
  2. 构建拦截器列表
  3. 处理Http请求映射核心组件
  4. RequestMapping处理器适配器,内部实例化了集成点对象
  5. 获取返回值处理器列表
  6. 获取参数解析器列表
  7. 定义全局异常处理器
  8. 视图解析

DelegatingWebMvcConfiguration

集中管理多个 WebMvcConfigurer接口的实现,允许多个独立的配置器一起工作,合并它们的配置,并通过覆盖父类中的方法将这些配置应用到Spring MVC中,以此提供一个灵活且强大的方式来自定义Spring MVC的行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

// 存储和执行所有注册的WebMvcConfigurer实例
private WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

/**
* 允许Spring自动查找并注入List<WebMvcConfigurer>类型的bean
* @param configurers
*/
@Autowired(required = false) // 表示依赖是可选的,如果没有找bean,不会抛异常,允许该方法不执行
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}

@Override
protected void addFormatters(FormatterRegistry registry) {
configurers.addFormatters(registry);
}

@Override
protected void addInterceptors(InterceptorRegistry registry) {
configurers.addInterceptors(registry);
}

@Override
protected void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
configurers.addReturnValueHandlers(returnValueHandlers);
}

@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
configurers.addArgumentResolvers(argumentResolvers);
}

@Override
protected void addDefaultViews(List<View> views) {
configurers.addDefaultViews(views);
}

@Override
protected void addViewResolvers(List<ViewResolver> viewResolvers) {
configurers.addViewResolvers(viewResolvers);
}
}

2. 组件间关系

WebMvcConfigurer 是一个接口,定义了一系列可以用来自定义和配置 Spring MVC的方法。

WebMvcConfigurerComposite 实现了 WebMvcConfigurer 接口,组织多个 WebMvcConfigurer 实现。集中管理多个配置,并将所有配置器的配置聚合到一个统一的配置点。

WebMvcConfigurationSupport 类提供了 Spring MVC 配置的基础设施支持,定义了一系列可以被子类覆盖的保护方法和创建必要 Spring MVC 组件的 @Bean 方法。

DelegatingWebMvcConfiguration 继承自 WebMvcConfigurationSupport,并使用 WebMvcConfigurerComposite 来整合多个 WebMvcConfigurer 实现。它主要的职责是将接收到的所有 WebMvcConfigurer 实现的配置聚集到一处,并通过覆盖父类(WebMvcConfigurationSupport)中的方法将这些配置应用到 Spring MVC 中。

集成和扩展WebMvcConfigurerComposite 将多个 WebMvcConfigurer 实例的功能集成到一个配置点。DelegatingWebMvcConfiguration 则使用这个组合,并将其作为桥梁通过 WebMvcConfigurationSupport 提供的基础设施将配置应用到 Spring MVC。

继承链和实现DelegatingWebMvcConfigurationWebMvcConfigurationSupport 的一个具体实现,它扩展了基础的 MVC 配置支持,并通过组合 WebMvcConfigurerComposite实现了对多个配置的管理和应用。

3. @EnableWebMvc

1
2
3
4
5
6
7
8
9
10
11
12
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
/*
* @Import(DelegatingWebMvcConfiguration.class)就相当于向Spring容器中注入了DelegatingWebMvcConfiguration实例
*
* 当在Spring配置上使用@EnableWebMvc注解时,实际是向Spring容器中注入了DelegatingWebMvcConfiguration,
* 即注入了Spring MVC所必要的基础设施,轻松地在Spring应用中启用和自定义了全面的Spring MVC支持
*/
}

与SpringBoot集成(一)

image

GenericWebApplicationContext集成了Web环境

ServletWebServerApplicaationContext集成了内嵌的Servlet容器

Spring MVC中

WebApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 扩展了ApplicationContext,专门用于Web环境
* 基于IOC容器ApplicationContext,提供了获取ServletContext的方法
*/
public interface WebApplicationContext extends ApplicationContext {

// root_web_application_context_attribute 存储在Web应用的ServletContext中,用于标识跟Web应用程序上下文的属性名称
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

// 与Web容器的交互入口点,用于获取Web应用的各种配置和上下文信息
ServletContext getServletContext();
}

ConfigurableWebApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 主要提供配置ApplicationContext的功能
* WebApplicationContext接口主要用于获取Web应用程序上下文,包括获取ServletContext、获取Web应用程序的配置信息等
* ConfigurableApplicationContext接口提供了配置ApplicationContext的功能
*/
public interface ConfigurableWebApplicationContext extends WebApplicationContext, ConfigurableApplicationContext {

/**
* 允许显式设置ServletContext,用于设置当前Application的ServletContext
* @param servletContext
*/
void setServletContext(ServletContext servletContext);
}

GenericWebApplicationContext(继承了 IocApplicationContext的实现类,拓展为一个通用的 web应用上下文)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 一个通用的Web应用上下文,提供了与Servlet容器集成的能力
*/
public class GenericWebApplicationContext extends GenericApplicationContext
implements ConfigurableWebApplicationContext {

// ServletContext的实例,用于存储与Servlet容器相关的信息,提供了与Servlet容器交互的能力
private ServletContext servletContext;

/**
* 构造函数,用于创建一个与给定的ServletContext相关联的Web应用上下文。
*
* @param servletContext Servlet上下文,提供与Servlet容器交互的能力。
*/
public GenericWebApplicationContext(ServletContext servletContext) {
this.servletContext = servletContext;
}

/**
* 默认构造函数,创建一个不与任何ServletContext关联的Web应用上下文。
*/
public GenericWebApplicationContext() {
}

/**
* 设置与当前Web应用上下文关联的ServletContext。
*
* @param servletContext 要设置的Servlet上下文。
*/
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}

/**
* 获取当前Web应用上下文关联的ServletContext。
*
* @return 关联的Servlet上下文。
*/
@Override
public ServletContext getServletContext() {
return this.servletContext;
}
}

Spring Boot中

ServletWebServerApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
* 又是一个应用上下文
* 主要目的在Spring Boot应用程序启动时创建和管理Web服务器,并将应用上下文与Web服务器集成
*/
public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements WebServerApplicationContext {

// 定义WebServer,存储创建的Web服务器实例。这是SpringBoot中的类,有多个实现:Tomcat,jetty等等
private WebServer webServer;

public ServletWebServerApplicationContext() {
}

@Override
public WebServer getWebServer() {
return this.webServer;
}

/**
* try-catch整个容器的refresh过程,一旦出现任何异常,都需要关闭掉WebServer
* @throws BeansException
* @throws IllegalStateException
*/
@Override
public final void refresh() throws BeansException, IllegalStateException {
try {
super.refresh();
} catch (RuntimeException ex) {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.stop();
}
throw ex;
}
}

/**
* onRefresh是IOC容器提供的方法,允许用户在容器启动过程中做一些事情,这里我们就来创建和启动Web服务器
*/
@Override
protected void onRefresh() {
super.onRefresh();
try {
this.webServer = createWebServer();
this.webServer.start();
} catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}

// 调用ServletWebServerFactory工厂创建Web服务器
private WebServer createWebServer() {
ServletWebServerFactory factory = getBeanFactory().getBean(ServletWebServerFactory.class);
return factory.getWebServer(this::selfInitialize);
}

// ServletContextInitializer 在Web容器启动完成后会回调此方法,下篇我们就会用到这个类,
// 向ServletContext中添加DispatchServlet,即初始化Servlet上下文
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
Map<String, ServletContextInitializer> beanMaps = getBeanFactory().getBeansOfType(ServletContextInitializer.class);
for (ServletContextInitializer bean : beanMaps.values()) {
bean.onStartup(servletContext);
}
}

/**
* 在Servlet上下文中存储ApplicationContext,并保存对ServletContext的引用。
* @param servletContext
*/
private void prepareWebApplicationContext(ServletContext servletContext) {
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
setServletContext(servletContext);
}
}

DispatcherServletRegistrationBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 一个用于注册DispatcherServlet的ServletRegistrationBean扩展,
* 同时实现了DispatcherServletPath接口,以提供Servlet的路径配置。
*/
public class DispatcherServletRegistrationBean extends ServletRegistrationBean<DispatcherServlet>
implements DispatcherServletPath {

private final String path; // Servlet的注册路径

/**
* 构造函数用于创建一个DispatcherServlet的注册bean,并配置其URL映射。
*
* @param servlet 要注册的DispatcherServlet实例。
* @param path Servlet的注册路径,不能为空。
*/
public DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) {
super(servlet);
Assert.notNull(path, "Path must not be null"); // 确保路径不为空
this.path = path;
super.addUrlMappings(getServletUrlMapping()); // 添加URL映射
}

/**
* 获取Servlet的注册路径。
*
* @return 返回Servlet的注册路径。
*/
@Override
public String getPath() {
return this.path;
}
}

与SpringBoot集成(二)

引入 Tomcat依赖

1
2
3
4
5
6
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>3.2.4</version>
<optional>true</optional>
</dependency>

ServletWebServerFactoryAutoConfiguration

创建 TomcatServletWebServerFactory,根据应用程序的环境和依赖情况自动配置 Tomcat作为嵌入式 Servlet容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* Spring Boot自动配置类,用于根据应用程序的环境和依赖情况自动配置Tomcat作为嵌入式Servlet容器
*/
// 表示该类为Spring配置类,集成了@Component注解说明该类会被自动注入,此类可以使用@Bean注解来初始化定义bean
@Configuration
// 条件注解,仅当类路径上存在ServletRequest.class时,当前配置类才会被考虑
@ConditionalOnClass(ServletRequest.class)
// 启用对ServerProperties类中定义的配置属性的支持,这使得Spring Boot的配置文件(如application.properties或application.yml)中的相关属性可以绑定到ServerProperties类的实例上
@EnableConfigurationProperties(ServerProperties.class)
public class ServletWebServerFactoryAutoConfiguration {

@Bean // 表示该方法将返回一个bean,由Spring容器管理
// 条件注解,进一步限定了Tomcat及其他相关类存在于类路径上时,这个bean才会被创建
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
// 只有当ServletWebServerFactory类型的bean不存在时,此方法配置的bean才会被创建
@ConditionalOnMissingBean(value = ServletWebServerFactory.class)
public TomcatServletWebServerFactory tomcatServletWebServerFactory(
ServerProperties serverProperties, // 服务器配置
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers, // 连接器定制器
ObjectProvider<TomcatContextCustomizer> contextCustomizers, // 上下文定制器
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) { // 协议处理器定制器

// 创建一个TomcatServletWebServerFactory实例
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();

// 将从Spring容器中获取的连接器、上下文和协议处理器定制器添加到工厂配置中
factory.getTomcatConnectorCustomizers() // 连接器定制器
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers() // 上下文定制器
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers() // 协议处理器定制器
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));

// 使用PropertyMapper工具类来简化属性之间的映射。此处,它确保仅当属性非空时才应用映射
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();

// map.from(...).to(...)语法用于从ServerProperties对象映射属性到TomcatServletWebServerFactory实例
map.from(serverProperties::getPort).to(factory::setPort);
map.from(serverProperties::getAddress).to(factory::setAddress);
map.from(serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
map.from(serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
map.from(serverProperties.getServlet()::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
map.from(serverProperties.getServlet()::getSession).to(factory::setSession);
map.from(serverProperties::getSsl).to(factory::setSsl);
map.from(serverProperties.getServlet()::getJsp).to(factory::setJsp);
map.from(serverProperties.getShutdown()).to(factory::setShutdown);

// 返回配置好的TomcatServletWebServerFactory实例
return factory;
}
}

DispatcherServletAutoConfiguration

创建 DispatcherServletDispatcherServletRegistrationBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* Spring Boot自动配置类,用于在Spring MVC应用中自动配置DispatcherServlet
* (即创建、配置和注册DispatcherServlet,使得Spring Boot能够根据条件自动配置和启用Spring MVC的核心控制器)
*/
// 标记该类为配置类
@Configuration
// 这个类的加载和注册取决于 DispatcherServlet.class 是否在类路径上可用,确保只有在使用Spring MVC时才应用这个配置
@ConditionalOnClass(DispatcherServlet.class)
// 启用 WebMvcProperties 类的配置属性支持,允许从外部配置源(如application.properties或application.yml)加载并绑定配置属性
@EnableConfigurationProperties(WebMvcProperties.class)
public class DispatcherServletAutoConfiguration {

// 定义默认的DispatcherServlet bean名称,这个名称将用于在Spring容器中注册bean
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "smartMvcDispatcherServlet";

/**
* 创建DispatcherServlet实例
* @return
*/
@Bean // 定义一个方法,该方法返回一个新的 DispatcherServlet 实例
// 仅在不存在 DispatcherServlet 类型的Bean时,才创建和注册新的 DispatcherServlet 实例。这确保了如果已经有其他配置提供了 DispatcherServlet,这个自动配置不会重复添加
@ConditionalOnMissingBean(value = DispatcherServlet.class)
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}

/**
* 注册DispatcherServlet到Servlet容器,并配置其URL映射和其他相关设置
* @param dispatcherServlet
* @param webMvcProperties 路径信息(从WebMvcProperties中获取)
* @return
*/
@Bean // 返回类型为自定义的 SmartMvcDispatcherServletRegistrationBean
// 该bean的创建依赖于 DispatcherServlet 类型的Bean的存在
@ConditionalOnBean(value = DispatcherServlet.class)
public DispatcherServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {

// 使用SmartMvcDispatcherServletRegistrationBean来注册DispatcherServlet,并通过传递的 WebMvcProperties 来设置Servlet的路径、名称和启动优先级
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
return registration;
}
}

自动化配置的入口类 WebMvcAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 负责配置Spring MVC的许多默认行为,通过一系列的条件检查和配置导入,提供了一个便捷的方式自动配置Spring MVC,同时也保留了一定的扩展性
*/
// 声明配置类
@Configuration
// 该配置类只有在容器中不存在WebMvcConfigurationSupport类型的Bean时才会生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
// 自动注入DispatcherServlet自动配置类和配置内嵌Servlet容器类
@Import({DispatcherServletAutoConfiguration.class, ServletWebServerFactoryAutoConfiguration.class})
public class WebMvcAutoConfiguration {

// 配置Spring MVC默认组件
@EnableWebMvc
// 使得WebMvcProperties类中定义的属性与Spring Boot的外部配置(如application.properties或application.yml文件)相绑定,允许开发者通过这些外部配置文件来设置Spring MVC的属性
@EnableConfigurationProperties({WebMvcProperties.class})
public static class EnableWebMvcAutoConfiguration {
}
}

spring.factories

spring.factories文件,用于在Spring Boot应用启动时自动发现并应用一系列预定义的配置类,由各种Spring Boot的starter包提供。

由键值对组成

  • 键通常表示一个接口,用于查找特定类型的组件
  • 值是与键相关联的具体实现类的全类名
1
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.boot.configurure.WebMvcAutoConfiguration

在这里就是通过识别 EnableAutoConfiguration注解来自动实现 WebMvcAutoConfiguration配置类