导言 Spring MVC是基于MVC架构(模型-视图-控制器)的高级Web框架,建立在Spring IOC容器之上,底层通过高度整合的组件实现了对Web应用中控制层的完整管理。基于Spring框架的强大基础,致力于提供一个高效、灵活的Web应用开发环境。
Spring MVC的核心组件 包括处理器映射器 、处理器适配器 、拦截器 等,核心组件的设计允许高度的模块化和可拓展性,通过共同协作,扩展并优化了请求的处理流程。
处理器映射器 建立请求路径与处理器间的映射关系,返回处理器链对象,保证了请求能被正确分发至相应的处理器;
处理器适配器 提供了强大的数据绑定机制,通过参数解析器与返回值处理器,自动将输入输出数据与Java应用的数据模型相连接,极大简化了前后端间流转数据的处理;
拦截器 则在此基础上提供了请求处理前后的拦截功能,增加了处理流程的灵活性和安全性。
DispatcherServlet 是整个MVC流程的中枢,负责协调各组件的执行,完成对HTTP请求的调度分发。
全局异常处理器 处理整个应用中抛出的异常,通过集中处理所有控制器层面的异常,统一管理错误处理逻辑,返回结构化的错误响应,显著提升了程序的健壮性。
核心配置类 利用Spring框架的控制反转和依赖注入,整合各个组件,管理和配置框架所需的各种资源和服务;将业务逻辑与配置逻辑分离,为应用的启动和运行提供了统一的配置入口,使得整个系统更加简洁和模块化。
整体架构 (蓝色为已经淘汰的JSP技术,前后端分离开发模式下并不用考虑)
1. 处理器映射器
处理器映射器-建立映射关系
拦截器
处理器映射器-返回处理器链对象
2. 处理器适配器
参数解析器
返回值处理器
Handler执行器
处理器适配器
3. 视图解析器
视图的渲染
视图解析器
<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 public @interface RequestMapping { String path () ; 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 public class RequestMappingInfo { private String path; private RequestMethod httpMethod; public RequestMappingInfo (String prefix, RequestMapping requestMapping) { this .path = prefix + requestMapping.path(); this .httpMethod = requestMapping.method(); } }
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 public class MappingRegistry { private Map<String, RequestMappingInfo> pathMappingInfo = new ConcurrentHashMap <>(); private Map<String, HandlerMethod> pathHandlerMethod = new ConcurrentHashMap <>(); 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); } }
2. 核心类&执行流程 2.1 概述 RequestMappingHandlerMapping
处理Http请求映射的核心组件
负责管理请求映射信息,以实现客户端请求到处理程序方法的路由和分发
2.2 具体步骤&类详解
解析 @RequestMapping
注解,获取请求信息
管理请求映射信息:将请求路径与处理程序方法 HandlerMethod
建立映射管理
在收到 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 () { Map<String, Object> beansOfMap = BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), Object.class); beansOfMap.entrySet().stream() .filter(entry -> this .isHandler(entry.getValue())) .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, (MethodIntrospector.MetadataLookup<RequestMappingInfo>) method -> getMappingForMethod(method, beanType)); methodsOfMap.forEach((method, requestMappingInfo) -> this .mappingRegistry.register(requestMappingInfo, handler, method)); } private RequestMappingInfo getMappingForMethod (Method method, Class<?> beanType) { RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class); if (Objects.isNull(requestMapping)) { return null ; } String prefix = getPathPrefix(beanType); return new RequestMappingInfo (prefix, requestMapping); } private String getPathPrefix (Class<?> beanType) { RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(beanType, RequestMapping.class); if (Objects.isNull(requestMapping)) { return "" ; } return requestMapping.path(); }
2.3 如何集成进上下文环境 上面所有的逻辑都被放入到 InitializingBean
的 afterPropertiesSet()
中,交由 ApplicationContext#refresh()
统一管理,从而实现集成!
1 2 3 4 5 @Override public void afterPropertiesSet () throws Exception { initialHandlerMethods(); }
3. 两个工具类 使用了两个工具类的三个方法
BeanFactoryUtils
beansOfTypeIncludingAncestors()
获取Spring容器中所有的bean及其beanName
AnnotatedElementUtils
4. 合并注解的原理及意义 by ChatGPT4
合并注解的原理是 Spring Framework 中用于处理注解继承、重写和组合的一种高级技术。它主要涉及到注解的合并策略,以及如何处理由多个注解或其属性构成的复杂关系。在 Spring 中,这个机制主要通过几个核心组件来实现:
核心组件和类
**AnnotatedElementUtils
**:这个类提供了静态方法来搜索带注解的元素(如类、方法或字段),并考虑了注解的继承关系和元注解。
MergedAnnotations
API :从 Spring 5.2 开始,Spring 引入了 MergedAnnotations
API,这是一个用于从类或方法等元素中获取注解信息的高性能、低内存占用的API。这个 API 能够处理注解的合并,并解析注解的层次结构。
**MergedAnnotation
**:表示一个可能来自多个源的合并后的注解。它不仅包含注解本身的信息,还包括合并过程中的元数据,如注解是否是显式声明的、继承的或通过元注解合成的。
合并原理的步骤 合并注解的过程可以分为以下几个步骤:
收集注解 :从给定的 AnnotatedElement
(可以是类、方法、字段等)收集所有相关的注解,包括直接声明的注解和通过继承获得的注解。
处理元注解 :元注解是应用于其他注解的注解。例如,@Transactional
可以用 @MetaAnnotation
标记。在处理一个注解时,Spring 也会查找其元注解,并将这些注解的属性合并到主注解中。
属性合并 :如果一个注解在继承链上多次出现,或者通过不同的元注解多次指定,Spring 需要确定哪些属性值将被采用。通常,更具体的定义(如在子类上的定义)会覆盖更一般的定义(如在父类上的定义)。
属性别名处理 :在注解定义中,可以通过 @AliasFor
指明属性别名,表明两个属性是等价的。在处理合并时,Spring 会确保别名之间的一致性,如果一个属性被设置了,它的别名也会相应地被设置。
最终合成 :通过上述步骤,Spring 构建了一个包含了所有合并逻辑的注解视图。最后,这个合并后的注解可以被应用于目标对象(如 Bean 定义),影响其行为。
目前来看意义在于灵活处理注解继承关系、元注解信息、多个注解同时使用怎么处理
拦截器 1. 核心类
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 public interface HandlerInterceptor { default boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true ; } default void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) 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 public class MappedInterceptor implements HandlerInterceptor { private List<String> includePatterns = new ArrayList <>(); private List<String> excludePatterns = new ArrayList <>(); private HandlerInterceptor interceptor; public MappedInterceptor (HandlerInterceptor interceptor) { this .interceptor = interceptor; } public HandlerInterceptor getInterceptor () { return interceptor; } public MappedInterceptor addIncludePatterns (String... patterns) { this .includePatterns.addAll(Arrays.asList(patterns)); return this ; } public MappedInterceptor addExcludePatterns (String... patterns) { this .excludePatterns.addAll(Arrays.asList(patterns)); return this ; } public boolean matches (String lookupPath) { if (!CollectionUtils.isEmpty(this .excludePatterns)) { if (excludePatterns.contains(lookupPath)) { return false ; } } if (ObjectUtils.isEmpty(this .includePatterns)) { return true ; } if (includePatterns.contains(lookupPath)) { return true ; } 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 <>(); 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 代理模式特点的体现
在原本对象的基础上增加额外:控制访问,前置条件检查
不改变接口:在外部看来仍然是一个拦截器,与代理模式定义相符
隐含策略模式
将url匹配逻辑封装为可配置的策略,自动根据不同的需求匹配拦截策略
处理器映射器-返回处理器链对象 HandlerMapping
获取对应的 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 { private HandlerMethod handler; private List<HandlerInterceptor> interceptors = new ArrayList <>(); private int interceptorIndex = -1 ; public HandlerExecutionChain (HandlerMethod handler, List<HandlerInterceptor> interceptors) { this .handler = handler; if (!CollectionUtils.isEmpty(interceptors)) { this .interceptors = interceptors; } } 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 ; } 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); } } 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; } }
这些行为的设计主要基于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 public interface HandlerMapping { 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; } @Override public HandlerExecutionChain getHandler (HttpServletRequest request) throws Exception { String lookupPath = request.getRequestURI(); HandlerMethod handler = mappingRegistry.getHandlerMethodByPath(lookupPath); if (Objects.isNull(handler)) { throw new NoHandlerFoundException (request); } return createHandlerExecutionChain(lookupPath, handler); } 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请求,同时提供清晰的业务逻辑分离和资源管理。
参数解析器
1. 组成组件 HttpServletRequest
,封装HTTP请求报文
HttpServletResponse
,封装HTTP响应报文
Model
,用于在控制器和视图之间传递数据,适用于MVC模式下的数据传递和视图渲染
Model
是一个在控制器和视图之间传递数据的容器,扮演桥梁的角色,用于存储控制器产生的数据,这些数据最终会被传递到视图层,并在视图层中被渲染到最终的HTML中。Model
本身只是作为数据的中介,不直接参与HTTP报文的构成。
用到的注解
@RequestParam
此注解用在请求handler方法的参数上,用于将http请求参数的值绑定到参数上。
@RequestBody
此注解用在请求handler方法的参数上,用于将http请求的Body映射绑定到此参数上。HttpMessageConverter
负责将对象转换为http请求。
Spring MVC的参数解析机制
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 public interface HandlerMethodArgumentResolver { boolean supportsParameter (MethodParameter parameter) ; Object resolveArgument (MethodParameter parameter, HttpServletRequest request, HttpServletResponse response, ModelAndViewContainer container, ConversionService conversionService) throws Exception;}
ServletRequestMethodArgumentResolver
专门解析 HttpServletRequest
类型的参数
ServletResponseMethodArgumentResolver
专门解析 HttpServletResponse
类型的参数
ModelMethodArgumentResolver
用于解析 Model
类型的参数。
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 { RequestParam param = parameter.getParameterAnnotation(RequestParam.class); if (Objects.isNull(param)) { return null ; } String value = request.getParameter(param.name()); if (StringUtils.isEmpty(value)) { value = param.defaultValue(); } if (!StringUtils.isEmpty(value)) { return conversionService.convert(value, parameter.getParameterType()); } if (param.required()) { throw new MissingServletRequestParameterException (parameter.getParameterName(), parameter.getParameterType().getName()); } return null ; }
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 { String httpMessageBody = this .getHttpMessageBody(request); if (!StringUtils.isEmpty(httpMessageBody)) { return JSON.parseObject(httpMessageBody, parameter.getParameterType()); } 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(); char [] buff = new char [1024 ]; int len; while ((len = reader.read(buff)) != -1 ) { sb.append(buff, 0 , len); } return sb.toString(); }
HandlerMethodArgumentResolverComposite
复合类,用于存储和管理多个 HandlerMethodArgumentResolver
实例。它允许将多个解析器组合在一起,并根据参数类型或注解自动选择合适的解析器来处理参数。(策略模式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList <>();@Override public Object resolveArgument (MethodParameter parameter, HttpServletRequest request, HttpServletResponse response, ModelAndViewContainer container, ConversionService conversionService) throws Exception { for (HandlerMethodArgumentResolver resolver : argumentResolvers) { if (resolver.supportsParameter(parameter)) { return resolver.resolveArgument(parameter, request, response, container, conversionService); } } 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 过程详解
用户交互 :用户在浏览器中发起一个请求,比如点击一个链接或提交一个表单。
HTTP请求 :这个请求以HTTP报文的形式发送到服务器。
控制器处理 :服务器的Spring MVC框架接收到这个HTTP请求,并根据路由信息将请求分派到相应的控制器方法。
数据处理与Model填充 :控制器执行相应的业务逻辑(如数据库查询等),并将结果数据填充到Model对象中。
视图渲染 :控制器返回一个视图名称,根据这个名称,Spring MVC找到相应的视图模板。视图模板使用Model中的数据来渲染生成HTML内容。
HttpServletResponse处理 :生成的HTML内容通过 HttpServletResponse
对象写入响应体。此外,HttpServletResponse
还用于设置HTTP响应的其他部分,如状态码、响应头等。
发送HTTP响应 :完整的HTTP响应(包括HTML内容和响应头等)发送回客户端(浏览器)。
客户端渲染 :浏览器接收到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 { boolean supportsReturnType (MethodParameter returnType) ; void handleReturnValue (@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, HttpServletRequest request, HttpServletResponse response) throws Exception;}
五个常用的返回值处理器,支持Handler返回 Map
、 Modle
、 View
、 ViewName
以及被 @ResponseBody
标注
主要处理 ModelAndView
的Model部分
ModelMethodReturnValueHandler
,用于处理返回值类型为Model的控制器方法
MapMethodReturnValueHandler
,用于处理返回值类型为Map的控制器方法
主要处理 ModelAndView
的View部分,分别返回视图名和视图对象
ViewNameMethodReturnValueHandler
,用于处理返回值类型为 CharSequence
的控制器方法
``ViewMethodReturnValueHandler`,用于处理返回值类型为View的控制器方法
用于将方法的返回对象直接输出到HTTP响应中
ResponseBodyMethodReturnValueHandler
,用于处理被 @ResponseBody
注解标注的控制器方法
五个返回值解析器的具体实现 大同小异
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 中所有字符序列类的超类,常见的实现类包括 String
、StringBuilder
和 StringBuffer
。它提供了一些方法来访问和操作字符序列,但并没有规定具体的存储方式或实现细节,这些方法被它的所有实现类所实现。
常见实现类:
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; } public void invokeAndHandle (HttpServletRequest request, HttpServletResponse response, ModelAndViewContainer mavContainer) throws Exception { List<Object> args = this .getMethodArgumentValues(request, response, mavContainer); Object resultValue = doInvoke(args); if (Objects.isNull(resultValue)) { if (response.isCommitted()) { mavContainer.setRequestHandled(true ); return ; } else { throw new IllegalStateException ("Controller handler return value is null" ); } } mavContainer.setRequestHandled(false ); Assert.state(this .returnValueHandler != null , "No return value handler" ); MethodParameter returnType = new MethodParameter (this .getMethod(), -1 ); this .returnValueHandler.handleReturnValue(resultValue, returnType, mavContainer, request, response); } private Object doInvoke (List<Object> args) throws InvocationTargetException, IllegalAccessException { return this .getMethod().invoke(this .getBean(), args.toArray()); } private List<Object> getMethodArgumentValues (HttpServletRequest request, HttpServletResponse response, ModelAndViewContainer mavContainer) throws Exception { Assert.notNull(argumentResolver, "HandlerMethodArgumentResolver can not null" ); List<MethodParameter> parameters = this .getParameters(); List<Object> args = new ArrayList <>(parameters.size()); for (MethodParameter parameter : parameters) { parameter.initParameterNameDiscovery(this .parameterNameDiscoverer); args.add(argumentResolver.resolveArgument(parameter, request, response, mavContainer, conversionService)); } return args; } public void setParameterNameDiscoverer (ParameterNameDiscoverer parameterNameDiscoverer) { this .parameterNameDiscoverer = parameterNameDiscoverer; } }
2. ParameterNameDiscoverer 正确的参数名称识别对于确保数据正确绑定到方法调用的参数上是至关重要的。
指定参数的映射时,如果没有准确的参数的名称,开发者可能需要依赖于参数的顺序或使用额外的注解来指定参数的映射,这可能会导致代码更加复杂且容易出错。因此,ParameterNameDiscoverer
是支持高效和精确自动装配及数据绑定功能的关键组件之一。
由于Java的反射API不能直接获取到方法参数的名称,所以该类底层提供了一系列的方法来确定方法参数的名称。
2.1 底层实现 (复制粘贴的,还只能看懂个大概,暂时先了解一下吧)
Spring 提供了几个 ParameterNameDiscoverer
的实现,每种实现使用不同的策略来确定方法参数的名称:
DefaultParameterNameDiscoverer : 这是一个复合实现,它按顺序尝试多个发现策略。默认情况下,它将尝试使用 StandardReflectionParameterNameDiscoverer
和 LocalVariableTableParameterNameDiscoverer
。
StandardReflectionParameterNameDiscoverer : 使用 Java 8 引入的反射能力,只在编译时使用了 -parameters
选项时才能获取参数名称。
LocalVariableTableParameterNameDiscoverer : 通过读取类的字节码中的局部变量表来发现参数名称。这需要方法在编译时包含调试信息(通常是使用 -g
选项)。
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 this .parameters = new ArrayList <>();int parameterCount = method.getParameterCount();for (int index = 0 ; index < parameterCount; index++) { parameters.add(new MethodParameter (method, index)); }
将实例化 MethodParameter
之前先复习一下 HandlerMethod
一个 HandlerMethod
对象封装的是一个 处理程序方法的类 中 对应控制器方法 的信息
四个参数
控制器类bean
bean类型
对应控制器方法对象
方法参数对象列表
然后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 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 public interface HandlerAdapter { 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 public class RequestMappingHandlerAdapter implements HandlerAdapter , InitializingBean { private List<HandlerMethodArgumentResolver> customArgumentResolvers; private HandlerMethodArgumentResolverComposite argumentResolverComposite; private List<HandlerMethodReturnValueHandler> customReturnValueHandlers; private HandlerMethodReturnValueHandlerComposite returnValueHandlerComposite; private ConversionService conversionService; @Override public ModelAndView handle (HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { InvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); ModelAndViewContainer mavContainer = new ModelAndViewContainer (); invocableMethod.invokeAndHandle(request, response, mavContainer); 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); } } }
3. ModelAndView&ModelAndViewContainer ModelAndView
和 ModelAndViewContainer
的区别与关系
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); } 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("/" )) { 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 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 { exposeModelAsRequestAttributes(model, request); RequestDispatcher rd = request.getRequestDispatcher(this .url); rd.forward(request, response); } 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; } }
视图解析器
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 public abstract class AbstractCachingViewResolver implements ViewResolver { private final Object lock = new Object (); private static final View UNRESOLVED_VIEW = (model, request, response) -> { }; private Map<String, View> cachedViews = new HashMap <>(); @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); if (Objects.isNull(view)) { view = UNRESOLVED_VIEW; } cachedViews.put(viewName, view); } return (view != UNRESOLVED_VIEW ? view : null ); } 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;public abstract class UrlBasedViewResolver extends AbstractCachingViewResolver { public static final String REDIRECT_URL_PREFIX = "redirect:" ; public static final String FORWARD_URL_PREFIX = "forward:" ; private String prefix = "" ; private String suffix = "" ; @Override protected View createView (String viewName) { if (viewName.startsWith(REDIRECT_URL_PREFIX)) { String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length()); return new RedirectView (redirectUrl); } if (viewName.startsWith(FORWARD_URL_PREFIX)) { String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); return new InternalResourceView (forwardUrl); } 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 public class InternalResourceViewResolver extends UrlBasedViewResolver { @Override protected View buildView (String viewName) { String url = getPrefix() + viewName + getSuffix(); return new InternalResourceView (url); } }
5. 拼接前后缀 解析 ViewName
主要分为两类
请求转发和响应重定向
服务器内部资源视图解析器(解析服务器内部的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 public abstract class RequestContextHolder { private static final ThreadLocal<HttpServletRequest> inheritableRequestHolder = new NamedInheritableThreadLocal <>("Request context" ); 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 public class ContentNegotiatingViewResolver implements ViewResolver , InitializingBean { private List<ViewResolver> viewResolvers = new ArrayList <>(); 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; } return null ; } private View getBestView (List<View> candidateViews) { Optional<View> viewOptional = candidateViews.stream() .filter(view -> view instanceof RedirectView) .findAny(); if (viewOptional.isPresent()) { return viewOptional.get(); } HttpServletRequest request = RequestContextHolder.getRequest(); Enumeration<String> acceptHeaders = request.getHeaders("Accept" ); while (acceptHeaders.hasMoreElements()) { for (View view : candidateViews) { if (acceptHeaders.nextElement().contains(view.getContentType())) { return view; } } } return null ; } private List<View> getCandidateViews (String viewName) throws Exception { List<View> candidateViews = new ArrayList <>(); for (ViewResolver viewResolver : viewResolvers) { View view = viewResolver.resolveViewName(viewName); if (Objects.nonNull(view)) { candidateViews.add(view); } } if (!CollectionUtils.isEmpty(defaultViews)) { candidateViews.addAll(defaultViews); } return candidateViews; } @Override public void afterPropertiesSet () throws Exception { Assert.notNull(viewResolvers, "viewResolvers can not null" ); } public void setViewResolvers (List<ViewResolver> viewResolvers) { this .viewResolvers = viewResolvers; } public void setDefaultViews (List<View> defaultViews) { this .defaultViews = defaultViews; } }
DispatcherServlet
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
主要分为两部分
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()); private ApplicationContext applicationContext; private HandlerMapping handlerMapping; private HandlerAdapter handlerAdapter; private ViewResolver viewResolver; private Collection<HandlerExceptionResolver> handlerExceptionResolvers; @Override public void setApplicationContext (ApplicationContext applicationContext) throws BeansException { this .applicationContext = applicationContext; } @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(); } @Override protected void service (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { logger.info("DispatcherServlet.service => uri:{}" , request.getRequestURI()); RequestContextHolder.setRequest(request); try { doDispatch(request, response); } catch (Exception e) { logger.error("Handler the request fail" , e); } finally { RequestContextHolder.resetRequest(); } } 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); if (!executionChain.applyPreHandle(request, response)) { return ; } mv = handlerAdapter.handle(request, response, executionChain.getHandler()); executionChain.applyPostHandle(request, response, mv); } catch (Exception e) { dispatchException = e; } processDispatchResult(request, response, mv, dispatchException); } catch (Exception ex) { dispatchException = ex; throw ex; } finally { if (Objects.nonNull(executionChain)) { executionChain.triggerAfterCompletion(request, response, dispatchException); } } } private void processDispatchResult (HttpServletRequest request, HttpServletResponse response, ModelAndView mv, Exception ex) throws Exception { if (Objects.nonNull(ex)) { mv = processHandlerException(request, response, ex); } if (Objects.nonNull(mv)) { render(mv, request, response); return ; } logger.info("No view rendering, null ModelAndView returned." ); } 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); } 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">
终于看懂了,熟悉使用是看源码的前提!如果连它的作用都不知道是什么,那源码肯定看不懂!
先举一个全局异常处理器实际使用的例子,看源码时要集合这个例子想象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 = new ResultInfo (0 ,"RuntimeException" ,null ); return resultInfo; } @ExceptionHandler(IOException.class) public ResultInfo ioHandleException (IOException e) { 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 public class ExceptionHandlerMethodResolver { public static final ReflectionUtils.MethodFilter EXCEPTION_HANDLER_METHODS = new ReflectionUtils .MethodFilter() { @Override public boolean matches (Method method) { return AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class); } }; private final Map<Class<? extends Throwable >, Method> mappedMethods = new ConcurrentReferenceHashMap <>(16 ); public ExceptionHandlerMethodResolver (Class<?> handlerType) { for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) { for (Class<? extends Throwable > exceptionType : detectExceptionMappings(method)) { this .mappedMethods.put(exceptionType, method); } } } private List<Class<? extends Throwable >> detectExceptionMappings(Method method) { ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class); 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(); } public Method resolveMethod (Exception exception) { Method method = resolveMethodByExceptionType(exception.getClass()); if (method == null ) { Throwable cause = exception.getCause(); if (cause != null ) { method = resolveMethodByExceptionType(cause.getClass()); } } return method; } private Method resolveMethodByExceptionType (Class<? extends Throwable> exceptionClass) { return mappedMethods.get(exceptionClass); } }
2.1 涉及的两个工具类 MethodIntrospector.selectMethods()
两个参数:
Class<?>
要进行方法选择的目标类
ReflectionUtils.MethodFilter
方法过滤器,用于定义哪些方法符合选择的条件
返回值:符合条件的方法对象列表
(又是两个工具类)
3. ControllerAdviceBean 包装类,用于封装和管理带有 @ControllerAdvice
注解的 bean
核心只有这个方法,返回所有 ControllerAdviceBean
实例的列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static List<ControllerAdviceBean> findAnnotatedBeans (ApplicationContext context) { Map<String, Object> beanMap = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, Object.class); return beanMap.entrySet().stream() .filter(entry -> hasControllerAdvice(entry.getValue())) .map(entry -> new ControllerAdviceBean (entry.getKey(), entry.getValue())) .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) { InvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(ex); if (exceptionHandlerMethod == null ) { return null ; } ModelAndViewContainer mavContainer = new ModelAndViewContainer (); 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 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" ); List<MethodParameter> parameters = this .getParameters(); List<Object> args = new ArrayList <>(parameters.size()); 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)); } 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 private Collection<HandlerExceptionResolver> handlerExceptionResolvers;
在 init
中完成初始化
1 2 this .handlerExceptionResolvers = this .applicationContext.getBeansOfType(HandlerExceptionResolver.class).values();
完成 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; }
在 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 { if (Objects.nonNull(ex)) { mv = processHandlerException(request, response, ex); } if (Objects.nonNull(mv)) { render(mv, request, response); return ; } logger.info("No view rendering, null ModelAndView returned." ); }
无论控制器方法返回的是响应数据还是视图,该方法最终都会被执行到,然后进行统一的异常处理。
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); if (!executionChain.applyPreHandle(request, response)) { return ; } mv = handlerAdapter.handle(request, response, executionChain.getHandler()); executionChain.applyPostHandle(request, response, mv); } catch (Exception e) { 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 作用与目的 异常链的主要目的是保留错误处理过程中的完整信息,使得调试和错误诊断更加高效。这样做的好处包括:
保持错误上下文 :在将异常从一个系统层次传递到另一个系统层次时,保持原始异常的信息,如具体的错误代码、失败的操作和其他诊断信息。
帮助诊断问题 :底层异常常常包含了引起错误的直接原因,而顶层异常可能只说明了错误的一般性质或上下文。所以需要在异常从一个系统层次传递到另一个系统层次时,保持原始异常的信息,允许开发者或最终用户获得更详细的错误报告,包含原始错误的完整链条,而不仅仅是最后一环。
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)); } }
WebMvcConfigurationSupport
集中配置了Spring MVC所需的组件,将其注入Spring容器当中,从而提供了 DispatchServlet
所有需要使用到的组件,并提供了一系列钩子,允许开发者自定义和扩展Spring MVC的行为。
提供的组件:
构建数据转换服务
构建拦截器列表
处理Http请求映射核心组件
RequestMapping
处理器适配器,内部实例化了集成点对象
获取返回值处理器列表
获取参数解析器列表
定义全局异常处理器
视图解析
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 { private WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite (); @Autowired(required = false) 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。
继承链和实现 :DelegatingWebMvcConfiguration
是 WebMvcConfigurationSupport
的一个具体实现,它扩展了基础的 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 {}
与SpringBoot集成(一)
GenericWebApplicationContext
集成了Web环境
ServletWebServerApplicaationContext
集成了内嵌的Servlet容器
Spring MVC中 WebApplicationContext
1 2 3 4 5 6 7 8 9 10 11 12 public interface WebApplicationContext extends ApplicationContext { String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT" ; ServletContext getServletContext () ; }
ConfigurableWebApplicationContext
1 2 3 4 5 6 7 8 9 10 11 12 13 public interface ConfigurableWebApplicationContext extends WebApplicationContext , ConfigurableApplicationContext { void setServletContext (ServletContext servletContext) ; }
GenericWebApplicationContext
(继承了 Ioc
中 ApplicationContext
的实现类,拓展为一个通用的 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 public class GenericWebApplicationContext extends GenericApplicationContext implements ConfigurableWebApplicationContext { private ServletContext servletContext; public GenericWebApplicationContext (ServletContext servletContext) { this .servletContext = servletContext; } public GenericWebApplicationContext () { } @Override public void setServletContext (ServletContext servletContext) { this .servletContext = servletContext; } @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 public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements WebServerApplicationContext { private WebServer webServer; public ServletWebServerApplicationContext () { } @Override public WebServer getWebServer () { return this .webServer; } @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; } } @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); } } private WebServer createWebServer () { ServletWebServerFactory factory = getBeanFactory().getBean(ServletWebServerFactory.class); return factory.getWebServer(this ::selfInitialize); } 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); } } 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 public class DispatcherServletRegistrationBean extends ServletRegistrationBean <DispatcherServlet> implements DispatcherServletPath { private final String path; public DispatcherServletRegistrationBean (DispatcherServlet servlet, String path) { super (servlet); Assert.notNull(path, "Path must not be null" ); this .path = path; super .addUrlMappings(getServletUrlMapping()); } @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 @Configuration @ConditionalOnClass(ServletRequest.class) @EnableConfigurationProperties(ServerProperties.class) public class ServletWebServerFactoryAutoConfiguration { @Bean @ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class}) @ConditionalOnMissingBean(value = ServletWebServerFactory.class) public TomcatServletWebServerFactory tomcatServletWebServerFactory ( ServerProperties serverProperties, // 服务器配置 ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers, // 连接器定制器 ObjectProvider<TomcatContextCustomizer> contextCustomizers, // 上下文定制器 ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory (); 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 map = PropertyMapper.get().alwaysApplyingWhenNonNull(); 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); return factory; } }
DispatcherServletAutoConfiguration
创建 DispatcherServlet
、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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @Configuration @ConditionalOnClass(DispatcherServlet.class) @EnableConfigurationProperties(WebMvcProperties.class) public class DispatcherServletAutoConfiguration { public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "smartMvcDispatcherServlet" ; @Bean @ConditionalOnMissingBean(value = DispatcherServlet.class) public DispatcherServlet dispatcherServlet () { return new DispatcherServlet (); } @Bean @ConditionalOnBean(value = DispatcherServlet.class) public DispatcherServletRegistrationBean dispatcherServletRegistration ( DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) { 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 @Configuration @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @Import({DispatcherServletAutoConfiguration.class, ServletWebServerFactoryAutoConfiguration.class}) public class WebMvcAutoConfiguration { @EnableWebMvc @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
配置类