当前位置: 首页 > news >正文

深入 Spring MVC 底层:从 DispatcherServlet 到自定义组件的全链路解析 - 实践

深入 Spring MVC 底层:从 DispatcherServlet 到自定义组件的全链路解析 - 实践

Spring MVC作为Spring生态的核心Web框架,其设计哲学源于经典的MVC模式,但通过创新的组件化设计实现了高度解耦。其核心架构包含三大核心组件:

  1. DispatcherServlet:前端控制器,承担请求分发中枢角色
  2. HandlerMapping:请求路由解析器,实现URL到处理方法的精准映射
  3. HandlerAdapter:处理器适配器,统一不同类型处理器的调用方式

这三个组件构成请求处理的核心管道,配合拦截器、视图解析器等辅助组件,形成完整的请求生命周期管理

一、Spring MVC 核心架构:DispatcherServlet 的 “中央调度” 模式

Spring MVC 的核心设计模式是前端控制器模式(Front Controller Pattern),而 DispatcherServlet 正是这个模式的实现者。它承担了请求分发、组件协调的核心职责,是整个 Spring MVC 框架的 “大脑”。

1.1 DispatcherServlet 的初始化流程

在我们的配置类MvcConfig中,通过@Bean注解显式定义了 DispatcherServlet:

@Bean
public DispatcherServlet dispatcherServlet() {
log.info("Creating DispatcherServlet instance");
return new DispatcherServlet();
}

当 Spring 容器初始化时,DispatcherServlet 会经历以下关键阶段:

  1. 构造阶段:创建 DispatcherServlet 实例,此时尚未初始化核心组件
  2. 初始化阶段:容器调用init()方法,触发onRefresh()钩子函数
  3. 策略初始化:在onRefresh()中调用initStrategies(),初始化九大核心组件
九大核心策略组件及其作用
组件类型核心作用默认实现自定义扩展点
MultipartResolver处理文件上传请求StandardServletMultipartResolver可实现自定义文件上传逻辑
HandlerMapping请求 URL 到处理器的映射RequestMappingHandlerMapping自定义 URL 匹配规则
HandlerAdapter调用处理器方法的适配器RequestMappingHandlerAdapter支持特殊参数类型解析
ViewResolver逻辑视图名到物理视图的解析InternalResourceViewResolver集成 Thymeleaf、Freemarker 等
LocaleResolver处理国际化资源AcceptHeaderLocaleResolver基于 Cookie/Session 的国际化
ThemeResolver管理应用主题FixedThemeResolver动态主题切换
HandlerExceptionResolver统一异常处理ExceptionHandlerExceptionResolver全局异常处理策略
RequestToViewNameTranslator自动生成视图名DefaultRequestToViewNameTranslator自定义视图名生成规则
FlashMapManager跨请求数据传递SessionFlashMapManager分布式环境下的 Flash 数据管理

1.2 DispatcherServlet 的注册机制

创建 DispatcherServlet 实例后,还需要将其注册到 Servlet 容器中,这一过程由DispatcherServletRegistrationBean完成:

@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
DispatcherServlet servlet, WebMvcProperties mvcProperties) {
DispatcherServletRegistrationBean bean =
new DispatcherServletRegistrationBean(servlet, "/");
bean.setLoadOnStartup(mvcProperties.getServlet().getLoadOnStartup());
return bean;
}

这里有两个关键配置:

  • URL 映射路径"/"表示拦截所有请求(除了 JSP 请求,由 Tomcat 默认 Servlet 处理)
  • 加载顺序loadOnStartup值决定 Servlet 的初始化时机,正数表示容器启动时初始化,数值越小优先级越高

二、请求映射的底层实现:RequestMappingHandlerMapping

请求映射是 Spring MVC 的核心功能之一,用户通过@GetMapping@PostMapping等注解定义的接口,最终都由RequestMappingHandlerMapping来管理和匹配。

2.1 请求映射的注册过程

DispatcherServletInitCase的调试代码中,我们可以看到请求映射的注册详情:

RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class)
;
mapping.getHandlerMethods().forEach((requestMappingInfo, handlerMethod) ->
{
logHandlerMethodDetails(handlerMethod, requestMappingInfo);
});

RequestMappingHandlerMapping在初始化时会执行以下步骤:

  1. 组件扫描:扫描所有带有@Controller@RestController注解的类
  2. 方法解析:解析类和方法上的@RequestMapping及其变体注解
  3. 条件封装:将 URL 路径、HTTP 方法、请求参数、请求头等条件封装为RequestMappingInfo
  4. 映射注册:将RequestMappingInfo与对应的HandlerMethod(封装控制器方法)存入MappingRegistry

2.2 请求匹配的核心逻辑

当请求到达时,RequestMappingHandlerMapping会执行复杂的匹配逻辑,找到最适合的处理器方法:

  1. 路径匹配:根据请求 URL 匹配@RequestMapping定义的路径模式(支持 Ant 风格通配符)
  2. 方法匹配:验证请求的 HTTP 方法(GET/POST/PUT 等)是否与注解定义一致
  3. 参数匹配:检查请求是否包含params属性指定的参数(如params = "id"要求必须包含 id 参数)
  4. 头信息匹配:验证请求头是否满足headers属性的要求(如headers = "X-Auth"要求包含 X-Auth 头)
  5. 内容类型匹配:根据consumes属性验证请求的Content-Type(如consumes = "application/json"

以代码中的getUser方法为例:

@GetMapping(value = "/user", params = "id", headers = "X-Auth", consumes = "application/json")
public String getUser(@RequestParam("id") Long id, @RequestHeader("X-Auth") String token) {
// 业务逻辑
}

该方法的匹配条件被封装为RequestMappingInfo,包含:

  • 路径:/dispatcher/servlet/user(类上的@RequestMapping与方法上的路径拼接)
  • HTTP 方法:GET
  • 参数条件:必须包含 id 参数
  • 请求头条件:必须包含 X-Auth 头
  • 内容类型:仅接受 application/json

三、自定义参数解析:HandlerMethodArgumentResolver 的实践

Spring MVC 默认支持多种参数类型的解析(如@RequestParam@RequestHeader等),但在实际开发中,我们常常需要自定义参数解析逻辑,例如统一的 Token 参数处理。

3.1 自定义参数解析器的实现

代码中的TokenArgumentResolver实现了HandlerMethodArgumentResolver接口,用于解析带有@Token注解的参数:

@Slf4j
public class TokenArgumentResolver
implements HandlerMethodArgumentResolver {
private static final String DEFAULT_TOKEN_HEADER = "X-Auth-Token";
private static final String DEFAULT_TOKEN_PARAM = "authToken";
private static final boolean DEFAULT_VALIDATE_TOKEN = true;
@Override
public boolean supportsParameter(@NonNull MethodParameter parameter) {
Token tokenAnnotation = parameter.getParameterAnnotation(Token.class)
;
if (nonNull(tokenAnnotation)) {
log.debug("Detected Token annotation on parameter: {}", parameter.getParameterName());
return true;
}
return false;
}
@Override
public Object resolveArgument(@NonNull MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
@NonNull NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) {
// 多路径获取token
String token = getHeaderToken(webRequest).orElseGet(() ->
getParameterToken(webRequest).orElse(null));
log.info("Resolved token: {}", maskToken(token));
if (DEFAULT_VALIDATE_TOKEN &&
nonNull(token)) {
validateJwtToken();
}
return token;
}
/**
* 从请求头获取token
*
* @param request Web请求
* @return Optional包装的token值
*/
private Optional<
String> getHeaderToken(NativeWebRequest request) {
// 可配置参数
String tokenHeader = DEFAULT_TOKEN_HEADER;
String headerValue = request.getHeader(tokenHeader);
log.debug("Checking header token from: {}", tokenHeader);
return Optional.ofNullable(headerValue);
}
/**
* 从请求参数获取token
*
* @param request Web请求
* @return Optional包装的token值
*/
private Optional<
String> getParameterToken(NativeWebRequest request) {
String tokenParam = DEFAULT_TOKEN_PARAM;
String paramValue = request.getParameter(tokenParam);
log.debug("Checking parameter token from: {}", tokenParam);
return Optional.ofNullable(paramValue);
}
/**
* JWT Token校验
*/
private void validateJwtToken() {
}
private String maskToken(String token) {
return token != null && token.length() >
4
? "****" + token.substring(token.length() - 4)
: "****";
}
}

3.2 参数解析器的注册与执行流程

自定义参数解析器需要注册到RequestMappingHandlerAdapter中才能生效:

@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
log.debug("Creating custom RequestMappingHandlerAdapter");
MyRequestMappingHandlerAdapter adapter = new MyRequestMappingHandlerAdapter();
// 添加自定义参数解析器
TokenArgumentResolver tokenResolver = new TokenArgumentResolver();
adapter.setCustomArgumentResolvers(List.of(tokenResolver));
// 添加自定义返回值处理器
JsonReturnValueHandler jsonHandler = new JsonReturnValueHandler();
adapter.setCustomReturnValueHandlers(List.of(jsonHandler));
log.info("Custom adapter configured with {} argument resolvers and {} return handlers",
Objects.requireNonNull(adapter.getCustomArgumentResolvers()).size(),
Objects.requireNonNull(adapter.getCustomReturnValueHandlers()).size());
return adapter;
}

参数解析的执行流程如下:

  1. 预处理:DispatcherServlet 接收到请求后,通过 HandlerMapping 找到对应的 HandlerMethod
  2. 适配准备:DispatcherServlet 调用 HandlerAdapter 的supports()方法判断是否支持该 HandlerMethod
  3. 参数解析:HandlerAdapter 遍历所有注册的 ArgumentResolver,通过supportsParameter()找到合适的解析器
  4. 方法调用:使用解析得到的参数值,通过反射调用目标处理器方法

四、自定义返回值处理:HandlerMethodReturnValueHandler 的应用

除了参数解析,Spring MVC 还允许我们自定义返回值的处理逻辑。代码中的JsonReturnValueHandler实现了HandlerMethodReturnValueHandler接口,用于将方法返回值自动序列化为 JSON 格式。

4.1 自定义返回值处理器的实现

@Slf4j
public class JsonReturnValueHandler
implements HandlerMethodReturnValueHandler {
private static final String DEFAULT_CHARSET = "UTF-8";
private static final String APPLICATION_JSON = MediaType.APPLICATION_JSON_VALUE;
@Override
public boolean supportsReturnType(MethodParameter returnType) {
log.debug("Checking return type support: {}", returnType.getParameterName());
return returnType.hasMethodAnnotation(JsonObject.class)
;
}
@Override
public void handleReturnValue(Object returnValue, @NonNull MethodParameter returnType, @NonNull ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
log.info("Handling JSON return value for method: {}", Objects.requireNonNull(returnType.getMethod()).getName());
// 获取响应对象
HttpServletResponse response = Objects.requireNonNull(
webRequest.getNativeResponse(HttpServletResponse.class)
,
"HttpServletResponse not found in request"
);
// 设置响应头
response.setContentType(APPLICATION_JSON);
response.setCharacterEncoding(DEFAULT_CHARSET);
log.debug("Set response content type to {}", APPLICATION_JSON);
// 序列化处理
String jsonStr = JSON.toJSON(returnValue);
log.debug("Serialized return value: {}", jsonStr);
// 写入响应体
response.getWriter().write(jsonStr);
response.getWriter().flush();
// 标记请求已处理
mavContainer.setRequestHandled(true);
log.debug("Marked request as fully handled");
}
}

4.2 返回值处理的核心机制

返回值处理的关键在于mavContainer.setRequestHandled(true)这行代码,它告诉 DispatcherServlet:

  • 该请求的响应已经完全处理完毕
  • 不需要再进行后续的视图解析和视图渲染流程
  • 直接返回响应给客户端

这与传统的 MVC 流程(处理器→ModelAndView→视图解析→渲染)不同,适用于 RESTful API 等不需要视图渲染的场景。

五、完整请求处理流程:从请求到响应的全链路

结合以上所有组件,我们可以梳理出 Spring MVC 完整的请求处理流程:

graph TD
A[客户端发送请求] --> B[Tomcat容器接收请求]
B --> C[DispatcherServlet拦截请求]
C --> D[调用HandlerMapping查找HandlerMethod]
D --> E[构建HandlerExecutionChain(包含拦截器)]
E --> F[调用HandlerAdapter适配处理器]
F --> G[HandlerAdapter调用ArgumentResolver解析参数]
G --> H[反射调用目标Controller方法]
H --> I[HandlerAdapter调用ReturnValueHandler处理返回值]
I --> J[通过拦截器的afterCompletion方法]
J --> K[返回响应给客户端]

各阶段的核心职责

  1. 请求接收阶段:Tomcat 容器将 HTTP 请求封装为 HttpServletRequest 对象
  2. 请求分发阶段:DispatcherServlet 作为前端控制器,协调各组件工作
  3. 处理器查找阶段:HandlerMapping 根据请求信息找到匹配的 HandlerMethod
  4. 参数解析阶段:ArgumentResolver 解析请求参数,为方法调用做准备
  5. 方法调用阶段:通过反射调用 Controller 的目标方法
  6. 返回值处理阶段:ReturnValueHandler 处理方法返回值,生成响应
  7. 响应返回阶段:将处理结果封装为 HTTP 响应,返回给客户端

六、自定义配置的最佳实践与扩展建议

基于本文的代码示例,我们可以总结出 Spring MVC 自定义配置的最佳实践:

6.1 组件扩展的原则

  1. 面向接口编程:自定义组件应实现 Spring 提供的标准接口(如 HandlerMethodArgumentResolver),而非继承具体实现类
  2. 优先级控制:通过setCustomArgumentResolvers而非addArgumentResolvers来确保自定义组件的优先级
  3. 职责单一:每个自定义组件应只负责单一功能(如 Token 解析、JSON 序列化)
  4. 可配置性:将硬编码的配置(如 Token 的请求头名称)改为可配置项,通过@ConfigurationProperties绑定

6.2 常见扩展场景

  1. 统一认证授权:扩展 TokenArgumentResolver,集成 JWT、OAuth2 等认证机制
  2. 统一返回格式:自定义 ReturnValueHandler,将所有接口返回值封装为统一格式(如 {code:200, data:{}, message:“success”})
  3. 特殊参数支持:实现自定义 ArgumentResolver,支持如日期、枚举等特殊类型的参数解析
  4. 全局异常处理:扩展 HandlerExceptionResolver,统一处理业务异常和系统异常

6.3 性能优化建议

  1. 组件复用:避免频繁创建自定义组件实例,通过 Spring Bean 管理实现复用
  2. 缓存机制:对请求映射、参数解析结果等进行缓存,减少重复计算
  3. 异步处理:对于耗时操作,使用@Async注解实现异步处理,提高系统吞吐量
  4. 拦截器优化:合理使用拦截器,避免在拦截器中执行耗时操作

七、总结

Spring MVC 通过 DispatcherServlet、HandlerMapping、HandlerAdapter 等核心组件,构建了一个松耦合、高可扩展的 MVC 框架。本文通过一份完整的自定义配置代码,深入解析了 Spring MVC 的底层原理,包括:

完整代码

package com.dwl.mvc;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/**
* @ClassName DispatcherServletController
* @Description
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
@Slf4j
@Controller
@RequestMapping("/dispatcher/servlet/")
public class DispatcherServletController
{
@GetMapping("index")
public String index() {
log.debug("Handling index request");
return "index";
}
@GetMapping("param")
public String param(@RequestParam("name") String name) {
log.info("Received param request with name: {}", name);
return name;
}
@GetMapping(value = "/user", params = "id", headers = "X-Auth", consumes = "application/json")
public String getUser(@RequestParam("id") Long id, @RequestHeader("X-Auth") String token) {
log.info("Processing user request - ID: {}, Token: {}", id, token);
return id + "+" + token;
}
@PutMapping("/putMapping")
public String putMapping(@Token String token) {
log.debug("PUT request received with token: {}", token);
return token;
}
@RequestMapping("/jsonObject")
@JsonObject
public User jsonObject() {
log.info("Generating JSON response");
User user = new User();
user.setName("Dwl");
user.setAge(18);
return user;
}
@Data
public static class User
{
private String name;
private Integer age;
}
}
package com.dwl.mvc;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @ClassName Json
* @Description
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonObject {
}
package com.dwl.mvc;
import com.dwl.json.JSON;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import java.util.Objects;
/**
* @ClassName JsonReturnValueHandler
* @Description JSON 返回值处理器
* 该处理器通过 {@link JsonObject} 注解标识需要 JSON 序列化的返回值,
* 自动将对象转换为 JSON 格式并设置响应头
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
@Slf4j
public class JsonReturnValueHandler
implements HandlerMethodReturnValueHandler {
private static final String DEFAULT_CHARSET = "UTF-8";
private static final String APPLICATION_JSON = MediaType.APPLICATION_JSON_VALUE;
@Override
public boolean supportsReturnType(MethodParameter returnType) {
log.debug("Checking return type support: {}", returnType.getParameterName());
return returnType.hasMethodAnnotation(JsonObject.class)
;
}
@Override
public void handleReturnValue(Object returnValue, @NonNull MethodParameter returnType, @NonNull ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
log.info("Handling JSON return value for method: {}", Objects.requireNonNull(returnType.getMethod()).getName());
// 获取响应对象
HttpServletResponse response = Objects.requireNonNull(
webRequest.getNativeResponse(HttpServletResponse.class)
,
"HttpServletResponse not found in request"
);
// 设置响应头
response.setContentType(APPLICATION_JSON);
response.setCharacterEncoding(DEFAULT_CHARSET);
log.debug("Set response content type to {}", APPLICATION_JSON);
// 序列化处理
String jsonStr = JSON.toJSON(returnValue);
log.debug("Serialized return value: {}", jsonStr);
// 写入响应体
response.getWriter().write(jsonStr);
response.getWriter().flush();
// 标记请求已处理
mavContainer.setRequestHandled(true);
log.debug("Marked request as fully handled");
}
}
package com.dwl.mvc;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.List;
import java.util.Objects;
/**
* @ClassName MvcConfig
* @Description MVC 核心配置类
* <p>
* 功能特性:
* 1. 自动扫描组件(排除 Controller 类,由其他配置处理)
* 2. 配置内嵌 Tomcat 容器参数
* 3. 注册 DispatcherServlet 到 Servlet 容器
* 4. 绑定 MVC 相关配置属性
* <p>
* 设计原理:
* - 基于 Spring Boot 自动配置机制,扩展 WebMvc 配置
* - 通过属性绑定实现配置文件与 Java 对象的映射
* - 遵循 Servlet 规范实现 Servlet 容器初始化
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
* @see DispatcherServlet Spring MVC 的核心调度器
* @see TomcatServletWebServerFactory Tomcat 容器工厂
*/
@Configuration
@ComponentScan
@PropertySource(
value = "classpath:application.yaml",
factory = YamlPropertySourceFactory.class // 自定义 YAML 属性源工厂
// 说明:默认只支持 properties 文件,需通过工厂解析 YAML 格式
)
@EnableConfigurationProperties({
// 启用配置属性绑定
WebMvcProperties.class,
// MVC 框架配置
ServerProperties.class // 服务器配置
})
@Slf4j
public class MvcConfig
{
/**
* 创建内嵌 Tomcat 容器工厂
* <p>
* 配置项来源:
* - application.yaml 中的 server.port
* - server.tomcat.connection-timeout
* 设计要点:
* - 通过参数注入 ServerProperties 实现配置解耦
* - 提供默认端口 8080 的容错机制
*/
@Bean
@Nullable
public TomcatServletWebServerFactory tomcatServletWebServerFactory(@Nullable ServerProperties serverProperties // 通过参数注入配置属性
) {
return new TomcatServletWebServerFactory(serverProperties != null ? serverProperties.getPort() : 8080);
}
/**
* 创建 DispatcherServlet 实例
* <p>
* 核心流程:
* 1. 初始化 Spring MVC 的核心调度器
* 2. 延迟初始化策略:由 Servlet 容器首次访问时初始化
* 3. 支持异步请求处理(Servlet 3.0+)
* 生命周期:
* - 构造阶段:创建 Servlet 实例
* - 初始化阶段:由容器调用 init() 方法
* - 销毁阶段:由容器调用 destroy() 方法
*/
@Bean
public DispatcherServlet dispatcherServlet() {
/*
* 在 onRefresh()方法中,DispatcherServlet 调用 initStrategies()初始化九大核心策略组件
* 组件类型 作用 默认实现
* MultipartResolver 处理文件上传请求 StandardServletMultipartResolver
* HandlerMapping 将请求 URL 映射到具体处理器(Controller) RequestMappingHandlerMapping(注解驱动)
* HandlerAdapter 调用处理器方法,支持不同参数解析和返回值处理 RequestMappingHandlerAdapter
* ViewResolver 将逻辑视图名解析为具体视图(如 JSP、Thymeleaf) InternalResourceViewResolver
* LocaleResolver 解析客户端区域信息,支持国际化 AcceptHeaderLocaleResolver
* ThemeResolver 解析应用主题(如 CSS 样式) FixedThemeResolver
* HandlerExceptionResolver 统一处理请求过程中的异常 ExceptionHandlerExceptionResolver
* RequestToViewNameTranslator 自动生成视图名(若未显式指定) DefaultRequestToViewNameTranslator
* FlashMapManager 管理跨请求数据传递(如重定向时传递参数) SessionFlashMapManager
*/
log.info("Creating DispatcherServlet instance");
return new DispatcherServlet();
}
/**
* 注册 DispatcherServlet 到 Servlet 容器
* <p>
* 关键配置项:
* - URL 映射路径:根路径 "/" 接收所有请求
* - 加载顺序:通过 loadOnStartup 控制初始化优先级
* - Servlet 名称:默认 "dispatcherServlet"
* 扩展点:
* - 可配置多个 DispatcherServlet 实现多应用上下文
* - 支持自定义 Servlet 初始化参数
*/
@Bean
@Nullable
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
@Nullable DispatcherServlet servlet, // 注入创建的 Servlet 实例
@Nullable WebMvcProperties mvcProperties // 绑定 MVC 配置属性
) {
// 处理可能的null值
int loadOnStartup = mvcProperties != null ? mvcProperties.getServlet().getLoadOnStartup() : -1;
// 没有其他路径,请求都会和'/'匹配,由servlet请求分发到其他的控制器上
DispatcherServletRegistrationBean bean = new DispatcherServletRegistrationBean(servlet != null ? servlet : new DispatcherServlet(), "/");
/*
* setLoadOnStartups设置大于0的值,会在tomcat启动时进行初始化
* 设置初始化顺序(数值越小优先级越高)
* 对应配置文件的配置
* spring:
* mvc:
* servlet:
* load-on-startup: 1
*/
bean.setLoadOnStartup(loadOnStartup);
return bean;
}
/**
* 1.默认行为
* DispatcherServlet在初始化时会从 Spring 容器中查找 HandlerMapping类型的 Bean,并将其作为成员变量。
* 默认实现:如果未显式配置 RequestMappingHandlerMapping,Spring 会通过 DispatcherServlet.properties文件中的默认配置加载 RequestMappingHandlerMapping(前提是容器中存在该 Bean)
* 自动注册:若容器中存在 @Controller或 @RestController注解的类,Spring 会自动扫描并注册 RequestMappingHandlerMapping,无需手动配置
* 2.显式配置的优先级
* 如果用户显式配置了自定义的 HandlerMapping(如 BeanNameUrlHandlerMapping),则 DispatcherServlet会优先使用显式配置的组件
* RequestMappingHandlerMapping是 Spring Boot 通过 WebMvcAutoConfiguration自动注册的 Bean。
* 未启用自动配置:若未使用 @SpringBootApplication或 @EnableAutoConfiguration,Spring 无法自动创建该 Bean
*/
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}
/**
* 自定义请求处理适配器
* <p>
* 扩展功能:
* - 添加Token参数解析器
* - 注册JSON返回值处理器
* - 自定义消息转换器
*
* @return 自定义适配器Bean
*/
@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
log.debug("Creating custom RequestMappingHandlerAdapter");
MyRequestMappingHandlerAdapter adapter = new MyRequestMappingHandlerAdapter();
// 添加自定义参数解析器
TokenArgumentResolver tokenResolver = new TokenArgumentResolver();
adapter.setCustomArgumentResolvers(List.of(tokenResolver));
// 添加自定义返回值处理器
JsonReturnValueHandler jsonHandler = new JsonReturnValueHandler();
adapter.setCustomReturnValueHandlers(List.of(jsonHandler));
log.info("Custom adapter configured with {} argument resolvers and {} return handlers",
Objects.requireNonNull(adapter.getCustomArgumentResolvers()).size(),
Objects.requireNonNull(adapter.getCustomReturnValueHandlers()).size());
return adapter;
}
}
package com.dwl.mvc;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.NonNull;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
/**
* @ClassName MyRequestMappingHandlerAdapter
* @Description 自定义请求映射处理器适配器
* <p>
* 扩展功能:
* 1. 增强请求参数校验
* 2. 添加请求处理日志
* 3. 支持自定义参数解析器
* 4. 增强返回值处理
* 5. 异常统一处理
* </p>
* <p>
* 设计原理:
* - 继承RequestMappingHandlerAdapter实现请求处理链扩展
* - 重写invokeHandlerMethod方法添加自定义逻辑
* - 通过参数解析器和返回值处理器实现扩展点
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
public class MyRequestMappingHandlerAdapter
extends RequestMappingHandlerAdapter {
@Override
public ModelAndView invokeHandlerMethod(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull HandlerMethod handlerMethod) throws Exception {
return super.invokeHandlerMethod(request, response, handlerMethod);
}
}
package com.dwl.mvc;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @ClassName Token
* @Description
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Token {
}
package com.dwl.mvc;
import io.micrometer.common.lang.Nullable;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import java.util.Optional;
import static java.util.Objects.nonNull;
/**
* @ClassName TokenArgumentResolver
* @Description Token参数解析器
* <p>
* 功能特性:
* 1. 自动解析@Token注解标记的参数
* 2. 支持请求头/请求参数双路径获取token
* 3. 集成JWT校验逻辑
* 4. 提供默认值配置
* 5. 异常处理机制
* </p>
* <p>
* 设计原理:
* - 实现HandlerMethodArgumentResolver接口
* - 优先从请求头获取token,回退到请求参数
* - 支持JWT格式token的自动解析
* - 集成Spring Security上下文
* </p>
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
@Slf4j
public class TokenArgumentResolver
implements HandlerMethodArgumentResolver {
private static final String DEFAULT_TOKEN_HEADER = "X-Auth-Token";
private static final String DEFAULT_TOKEN_PARAM = "authToken";
private static final boolean DEFAULT_VALIDATE_TOKEN = true;
@Override
public boolean supportsParameter(@NonNull MethodParameter parameter) {
Token tokenAnnotation = parameter.getParameterAnnotation(Token.class)
;
if (nonNull(tokenAnnotation)) {
log.debug("Detected Token annotation on parameter: {}", parameter.getParameterName());
return true;
}
return false;
}
@Override
public Object resolveArgument(@NonNull MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
@NonNull NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) {
// 多路径获取token
String token = getHeaderToken(webRequest).orElseGet(() ->
getParameterToken(webRequest).orElse(null));
log.info("Resolved token: {}", maskToken(token));
if (DEFAULT_VALIDATE_TOKEN &&
nonNull(token)) {
validateJwtToken();
}
return token;
}
/**
* 从请求头获取token
*
* @param request Web请求
* @return Optional包装的token值
*/
private Optional<
String> getHeaderToken(NativeWebRequest request) {
// 可配置参数
String tokenHeader = DEFAULT_TOKEN_HEADER;
String headerValue = request.getHeader(tokenHeader);
log.debug("Checking header token from: {}", tokenHeader);
return Optional.ofNullable(headerValue);
}
/**
* 从请求参数获取token
*
* @param request Web请求
* @return Optional包装的token值
*/
private Optional<
String> getParameterToken(NativeWebRequest request) {
String tokenParam = DEFAULT_TOKEN_PARAM;
String paramValue = request.getParameter(tokenParam);
log.debug("Checking parameter token from: {}", tokenParam);
return Optional.ofNullable(paramValue);
}
/**
* JWT Token校验
*/
private void validateJwtToken() {
}
private String maskToken(String token) {
return token != null && token.length() >
4
? "****" + token.substring(token.length() - 4)
: "****";
}
}
package com.dwl.mvc;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import java.util.Properties;
/**
* @ClassName YamlPropertySourceFactory
* @Description
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
public class YamlPropertySourceFactory
implements PropertySourceFactory {
@Override
@NonNull
public PropertySource<
?> createPropertySource(@Nullable String name, EncodedResource resource) {
// 1. 创建 YAML 解析器
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
// 2. 解析 YAML 到 Properties 对象
Properties properties = factory.getObject();
// 3. 获取文件名(处理空名情况)
String sourceName = name != null ? name : resource.getResource().getFilename();
// 4. 创建 Spring 可识别的属性源
assert properties != null;
return new PropertiesPropertySource(sourceName != null ? sourceName : "yaml", properties);
}
}
package com.dwl.mvc;
import com.dwl.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @ClassName DispatcherServletInitCase
* @Description DispatcherServlet初始化调试案例
* <p>
* 核心功能:
* 1. 请求映射注册的底层实现跟踪
* 2. 请求处理链的完整执行流程
* 3. 参数解析与返回值处理的调试信息
* 4. 拦截器执行顺序的可视化监控
* </p>
* <p>
* 设计原理:
* - 基于Spring MVC的DispatcherServlet初始化流程
* - 通过RequestMappingHandlerMapping解析请求映射
* - 使用HandlerExecutionChain管理拦截器链
* - 通过HandlerAdapter调用目标处理器方法
* </p>
* @Version 1.0.0
* @Date 2025
* @Author By Dwl
*/
@Slf4j
public class DispatcherServletInitCase
{
public static void main(String[] args) throws Exception {
// 初始化Spring Web容器
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(MvcConfig.class)
;
/*
* 1.请求映射管理
* 扫描与注册:自动扫描所有带有 @Controller或 @RestController注解的类,解析类和方法上的 @RequestMapping及其变体注解(如 @GetMapping、@PostMapping),生成 请求映射规则(RequestMappingInfo)。
* 映射表维护:将生成的 RequestMappingInfo与对应的 HandlerMethod(封装了控制器方法的对象)关联,存储在内部的 MappingRegistry中,形成请求到处理方法的映射表。
*
* 2.请求匹配与路由
* 请求解析:当请求到达时,解析请求的 URL、HTTP 方法、请求参数、请求头等信息。
* 匹配逻辑:根据 RequestMappingInfo中的条件(路径模式、HTTP 方法、参数条件等),从映射表中筛选出最匹配的 HandlerMethod。
* 返回执行链:将匹配的 HandlerMethod封装为 HandlerExecutionChain(包含拦截器链),返回给 DispatcherServlet执行
*/
RequestMappingHandlerMapping mapping =
context.getBean(RequestMappingHandlerMapping.class)
;
log.info("========== 请求映射注册详情 ==========");
// 遍历所有已注册的处理器方法
mapping.getHandlerMethods().forEach((requestMappingInfo, handlerMethod) ->
{
logHandlerMethodDetails(handlerMethod, requestMappingInfo);
});
set(context, null, "/dispatcher/servlet/index", "GET");
set(context, Map.of("name", "Dwl"), "/dispatcher/servlet/param", "GET");
set(context, Map.of("token", "Dwl"), "/dispatcher/servlet/putMapping", "PUT");
set(context, null, "/dispatcher/servlet/jsonObject", "POST");
}
/**
* 打印处理器方法详细信息
*
* @param handlerMethod 处理器方法
* @param mappingInfo 请求映射信息
*/
private static void logHandlerMethodDetails(HandlerMethod handlerMethod,
RequestMappingInfo mappingInfo) {
log.info("【处理器方法】{}", handlerMethod.getMethod().toGenericString());
log.info("【所属类】{}", handlerMethod.getBeanType().getSimpleName());
// 解析请求映射条件
log.info("【请求路径】{}", getPathPatterns(mappingInfo));
log.info("【HTTP方法】{}", getHttpMethods(mappingInfo));
log.info("【参数条件】{}", getParamsConditions(mappingInfo));
log.info("【请求头】{}", getHeadersConditions(mappingInfo));
log.info("【内容类型】{}", getConsumesConditions(mappingInfo));
log.info("【响应类型】{}", getProducesConditions(mappingInfo));
log.info("----------------------------------------");
}
/**
* 请求条件解析工具方法
*/
private static String getPathPatterns(RequestMappingInfo info) {
return Objects.requireNonNull(info.getPatternsCondition()).getPatterns().stream()
.map(path -> path + (path.endsWith("/") ? "" : "/"))
.collect(Collectors.joining(" | "));
}
private static String getHttpMethods(RequestMappingInfo info) {
return info.getMethodsCondition().getMethods().stream()
.map(RequestMethod::name)
.collect(Collectors.joining(" | "));
}
private static String getParamsConditions(RequestMappingInfo info) {
return info.getParamsCondition().getExpressions().stream()
.map(Object::toString)
.collect(Collectors.joining(" & "));
}
private static String getHeadersConditions(RequestMappingInfo info) {
return info.getHeadersCondition().getExpressions().stream()
.map(Object::toString)
.collect(Collectors.joining(" & "));
}
private static String getConsumesConditions(RequestMappingInfo info) {
return info.getConsumesCondition().getConsumableMediaTypes().stream()
.map(MediaType::toString)
.collect(Collectors.joining(" | "));
}
private static String getProducesConditions(RequestMappingInfo info) {
return info.getProducesCondition().getProducibleMediaTypes().stream()
.map(MediaType::toString)
.collect(Collectors.joining(" | "));
}
public static void set(AnnotationConfigServletWebServerApplicationContext context,
Map<
String, ?> params,
String requestURI,
String method) throws Exception {
// 创建请求对象
MockHttpServletRequest request = new MockHttpServletRequest(method, requestURI);
if (params != null) {
params.forEach((k, v) ->
{
request.addParameter(k, v.toString());
request.addHeader(k, v.toString());
// 模拟Header参数
});
}
// 获取处理链(包含拦截器和处理器)
HandlerExecutionChain chain =
context.getBean(RequestMappingHandlerMapping.class)
.getHandler(request);
log.info("【处理链信息】拦截器数量: {}", Objects.requireNonNull(Objects.requireNonNull(chain).getInterceptors()).length);
log.info("【目标处理器】{}", chain.getHandler());
// 创建响应对象
MockHttpServletResponse response = new MockHttpServletResponse();
// 获取自定义适配器
MyRequestMappingHandlerAdapter adapter =
context.getBean(MyRequestMappingHandlerAdapter.class)
;
// 执行请求处理流程
log.info("========== 开始处理请求 ==========");
adapter.invokeHandlerMethod(request, response,
ObjectUtil.cast(chain.getHandler()));
// 打印处理链信息
log.info("========== 处理链执行完成 ==========");
log.info("【响应状态】{}", response.getStatus());
log.info("【响应内容】{}", response.getContentAsString());
// 打印组件信息
printComponentDetails(adapter);
byte[] arr = response.getContentAsByteArray();
System.out.println(new String(arr, StandardCharsets.UTF_8));
}
private static void printComponentDetails(MyRequestMappingHandlerAdapter adapter) {
log.info("========== 组件信息 ==========");
Objects.requireNonNull(adapter.getArgumentResolvers()).forEach(resolver ->
{
log.info("参数解析器: {}", resolver.getClass().getSimpleName());
});
Objects.requireNonNull(adapter.getReturnValueHandlers()).forEach(handler ->
{
log.info("返回值处理器: {}", handler.getClass().getSimpleName());
});
}
}
http://www.wxhsa.cn/company.asp?id=1930

相关文章:

  • podman 替代docker
  • 202404_古剑山杯_数独
  • m1芯片装windows系统使用感受
  • mac book怎么切换windows系统
  • 硬件内在函数
  • 202205_宁波市赛_DocDocDoc
  • DP题
  • LGP7115 [NOIP 2020] 移球游戏 学习笔记
  • 阿里为何建议MVC+Manager层混合架构?
  • Android(Kotlin)+ ML Kit:移动端英文数字验证码识别实战
  • 用Android(Kotlin)+ ML Kit:移动端英文数字验证码识别实战
  • “人工智能+”的坚硬内核,边缘地带的“数字火种”:大模型如何烧出一片新天地
  • 详细介绍:10:00开始面试,10:06就出来了,问的问题有点变态。。。
  • PHP启动报错:liboing.so.5:cannot op如何处理?
  • 时空倒流 Time - 题解
  • 202508_QQ_XORPNG
  • Voice Agent 全球开发者比赛,TEN Dev Challenge 2025 等你来战!
  • 第02周 预习:Java基础语法2、面向对象入门 - hohohoho--
  • 第六届机器学习与计算机应用国际学术会议(ICMLCA 2025)
  • 设计模式-享源模式 - MaC
  • # 数论知识讲解与C++代码:唯一分解定理、辗转相除法、埃氏筛与线性筛(含质因数分解示例)
  • 第九届交通工程与运输系统国际学术会议(ICTETS 2025)
  • 小红书开源 FireRedTTS-2;全栈开源应用+嵌入式+电路设计:BUDDIE AI 语音交互方案丨日报
  • 设计模式-外观模式 - MaC
  • 深度解析 ADC 偶联技术:从随机偶联到定点偶联,如何平衡抗肿瘤 ADC 的活性、稳定性与均一性?
  • 豆包P图大更新,网友们已经玩嗨了。
  • 【初赛】无向图度数性质 - Slayer
  • $p\oplus q=r$
  • 2025年金融行业API安全最佳实践:构建纵深防御体系
  • Jack-of-All-Trades