揭秘 Spring MVC:手写一个迷你 DispatcherServlet 处理 @RequestParam 与 @ResponseBody
当一个网络请求到达我们的 Spring Boot 应用时,一场精心编排的“交响乐”就开始演奏了。@RestController
就像舞台,@GetMapping
负责指路,@RequestParam
负责递上道具,而 @ResponseBody
则决定了最终的谢幕方式。这场演出的总指挥,就是 DispatcherServlet
。
Chapter 1: The DispatcherServlet
Lifecycle - Spring MVC 的心脏
在 Spring MVC 的世界里,所有请求都会先经过 DispatcherServlet
。它的核心职责可以用一个流程来概括:
- 请求分发 (Dispatch):
DispatcherServlet
接收到请求后,会向HandlerMapping
(处理器映射器) 询问:“这个 URL 应该由谁来处理?”HandlerMapping
会返回一个处理器(通常是我们 Controller 里的一个具体方法)。 - 适配与调用 (Adapt & Invoke):
DispatcherServlet
不会直接调用这个方法,而是将它交给HandlerAdapter
(处理器适配器)。HandlerAdapter
是一个非常关键的角色,它负责真正地调用目标方法。在调用之前,它会“聪明”地解析方法的参数,看到@RequestParam
就去请求参数里取值,看到@RequestBody
就去读请求体。 - 返回值处理 (Return Value Handling): 方法执行完毕后,
HandlerAdapter
会将返回值告诉DispatcherServlet
。如果方法或类上带有@ResponseBody
注解,DispatcherServlet
会通过HttpMessageConverter
(消息转换器) 将返回的 Java 对象序列化成 JSON(或其他格式),然后写入 HTTP 响应。
我们的任务,就是模拟这个流程,特别是 HandlerMapping
和 HandlerAdapter
的核心功能。
Chapter 2: The Building Blocks - 新的注解与组件
首先,定义好我们的注解和模拟的 Web 环境。
2.1 Web 注解
// @MiniRestController: 标记这是一个控制器,且所有方法默认返回 JSON
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MiniRestController {}
// @MiniGetMapping: 标记一个方法处理 GET 请求
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MiniGetMapping {
String value(); // 用于定义 URL 路径
}
// @MiniRequestParam: 从请求参数中获取值并绑定到方法参数上
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface MiniRequestParam {
String value(); // 请求参数的名字
}
为了简化,我们约定 @MiniRestController
就隐含了所有方法都按 @ResponseBody
方式处理,这与 Spring 的 @RestController
行为一致。
2.2 模拟 Web 环境
import java.util.Map;
// 模拟 HTTP 请求
public class MiniHttpServletRequest {
private String path;
private Map<String, String> parameters;
public MiniHttpServletRequest(String path, Map<String, String> parameters) { this.path = path; this.parameters = parameters; }
public String getPath() { return path; }
public String getParameter(String name) { return parameters.get(name); }
}
// 模拟 HTTP 响应
public class MiniHttpServletResponse {
private String body;
public void setBody(String body) { this.body = body; }
public String getBody() { return body; }
}
2.3 处理器元数据
HandlerMethod
: 用于封装一个准备被调用的 Controller 方法的所有信息。
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class HandlerMethod {
private final Object bean; // 方法所在的 Bean 实例
private final Method method; // 方法本身
private final Parameter[] parameters; // 方法的参数列表
public HandlerMethod(Object bean, Method method) {
this.bean = bean;
this.method = method;
this.parameters = method.getParameters();
}
// Getters...
}
Chapter 3: The Engine Room - 构建 MiniDispatcherServlet
这个类是本次实战的核心,它将使用我们之前构建的 MiniApplicationContext
。
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;
public class MiniDispatcherServlet {
// 模拟 HandlerMapping,存储 URL 到处理方法的映射
private final Map<String, HandlerMethod> handlerMapping = new HashMap<>();
private final MiniApplicationContext context;
public MiniDispatcherServlet(MiniApplicationContext context) {
this.context = context;
initHandlerMappings();
}
/**
* 初始化 HandlerMapping,在启动时完成
*/
private void initHandlerMappings() {
System.out.println("--- 初始化 Handler Mappings ---");
// 从 IoC 容器中获取所有的 Bean
for (Object bean : context.getAllBeans().values()) {
Class<?> clazz = bean.getClass();
if (clazz.isAnnotationPresent(MiniRestController.class)) {
// 遍历 Controller 的所有方法
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(MiniGetMapping.class)) {
MiniGetMapping mapping = method.getAnnotation(MiniGetMapping.class);
String path = mapping.value();
// 创建 HandlerMethod 并注册
handlerMapping.put(path, new HandlerMethod(bean, method));
System.out.printf(" [Mapping] 发现处理器方法: %s -> %s.%s\n", path, clazz.getSimpleName(), method.getName());
}
}
}
}
}
/**
* 处理请求的核心方法
*/
public void doDispatch(MiniHttpServletRequest request, MiniHttpServletResponse response) throws Exception {
System.out.printf("\n--- 接收到请求: %s ---\n", request.getPath());
// 1. 根据请求路径查找处理器方法
HandlerMethod handler = handlerMapping.get(request.getPath());
if (handler == null) {
System.out.println(" [Error] 404 Not Found. 没有找到处理器。");
return;
}
// 2. 调用处理器方法(模拟 HandlerAdapter 的工作)
Object returnValue = invokeHandler(request, handler);
// 3. 处理返回值(模拟 HttpMessageConverter 的工作)
handleReturnValue(returnValue, response);
}
}
Chapter 4: The Real Magic - 参数解析与返回值处理
doDispatch
方法将最复杂的工作委托给了 invokeHandler
和 handleReturnValue
。这正是模拟 HandlerAdapter
的部分。
MiniDispatcherServlet.java
(添加 invoke
和 handle
方法)
// ... 接上文
public class MiniDispatcherServlet {
// ...
/**
* 模拟 HandlerAdapter 的功能:解析参数并调用方法
*/
private Object invokeHandler(MiniHttpServletRequest request, HandlerMethod handler) throws Exception {
System.out.println(" [Adapter] 开始解析参数并调用方法...");
Parameter[] parameters = handler.getParameters();
Object[] args = new Object[parameters.length];
// 遍历方法的每一个参数,看有什么注解,然后从请求中取值
for (int i = 0; i < parameters.length; i++) {
Parameter param = parameters[i];
if (param.isAnnotationPresent(MiniRequestParam.class)) {
MiniRequestParam requestParam = param.getAnnotation(MiniRequestParam.class);
String paramName = requestParam.value();
String paramValue = request.getParameter(paramName);
// 简单的类型转换(真实 Spring 中更复杂)
if (param.getType() == Integer.class) {
args[i] = Integer.parseInt(paramValue);
} else {
args[i] = paramValue;
}
System.out.printf(" - 解析到 @RequestParam: '%s' -> '%s'\n", paramName, paramValue);
}
}
System.out.println(" [Adapter] 参数解析完成,准备调用...");
// 通过反射调用 Controller 的方法
return handler.getMethod().invoke(handler.getBean(), args);
}
/**
* 模拟 HttpMessageConverter,将返回值序列化为 JSON 字符串
*/
private void handleReturnValue(Object returnValue, MiniHttpServletResponse response) {
System.out.println(" [Converter] 开始处理返回值...");
// 简单模拟 JSON 序列化
// 真实 Spring 使用 Jackson 等库
if (returnValue instanceof Map) {
StringBuilder json = new StringBuilder("{");
for (Object entry : ((Map<?, ?>) returnValue).entrySet()) {
Map.Entry<?, ?> mapEntry = (Map.Entry<?, ?>) entry;
json.append("\"").append(mapEntry.getKey()).append("\":\"").append(mapEntry.getValue()).append("\",");
}
if (json.length() > 1) json.deleteCharAt(json.length() - 1);
json.append("}");
response.setBody(json.toString());
} else {
response.setBody(returnValue.toString());
}
System.out.printf(" [Converter] 返回值处理完成,响应体为: %s\n", response.getBody());
}
}
Chapter 5: Putting It All Together - 组装与验证
我们来创建一个 Controller,并用 Main
方法模拟一次完整的请求-响应流程。
UserController.java
import java.util.Map;
@MiniRestController // 标记为控制器
public class UserController {
@MiniGetMapping("/user/info") // 映射到 /user/info
public Map<String, String> getUserInfo(
@MiniRequestParam("userId") Integer userId, // 绑定名为 userId 的请求参数
@MiniRequestParam("name") String name
) {
System.out.printf(" [Controller] 进入 getUserInfo 方法,参数: userId=%d, name=%s\n", userId, name);
return Map.of(
"userId", userId.toString(),
"name", name,
"from", "Mini-Spring-MVC"
);
}
}
MainApplication.java
import java.util.Map;
public class MainApplication {
public static void main(String[] args) throws Exception {
// 1. 启动 IoC 容器,注册我们的 Controller
MiniApplicationContext context = new MiniApplicationContext(UserController.class);
// 2. 启动我们的 DispatcherServlet,它会自动初始化 HandlerMapping
MiniDispatcherServlet dispatcherServlet = new MiniDispatcherServlet(context);
// 3. 模拟一个 HTTP GET 请求,访问 /user/info,并带有两个参数
MiniHttpServletRequest request = new MiniHttpServletRequest(
"/user/info",
Map.of("userId", "123", "name", "Geek-Player")
);
MiniHttpServletResponse response = new MiniHttpServletResponse();
// 4. 将请求交给 DispatcherServlet 处理
dispatcherServlet.doDispatch(request, response);
// 5. 打印最终的响应结果
System.out.println("\n--- 请求处理完毕,最终响应体 ---");
System.out.println(response.getBody());
}
}
运行 MainApplication
,你将看到一条清晰的请求处理链:
... (IoC 容器实例化日志) ...
--- 初始化 Handler Mappings ---
[Mapping] 发现处理器方法: /user/info -> UserController.getUserInfo
--- 接收到请求: /user/info ---
[Adapter] 开始解析参数并调用方法...
- 解析到 @RequestParam: 'userId' -> '123'
- 解析到 @RequestParam: 'name' -> 'Geek-Player'
[Adapter] 参数解析完成,准备调用...
[Controller] 进入 getUserInfo 方法,参数: userId=123, name=Geek-Player
[Converter] 开始处理返回值...
[Converter] 返回值处理完成,响应体为: {"name":"Geek-Player","userId":"123","from":"Mini-Spring-MVC"}
--- 请求处理完毕,最终响应体 ---
{"name":"Geek-Player","userId":"123","from":"Mini-Spring-MVC"}
Conclusion: The MVC Symphony - MVC 各组件的协同演奏
这次的旅程,我们从核心容器走向了 Web 世界。与 DI 和 AOP 在启动时“一次性”的处理不同,Web 层注解的处理是在每次请求时动态发生的。
核心思想总结:
- 统一入口,分层处理:
DispatcherServlet
作为唯一入口,将复杂的请求处理流程,委托给了HandlerMapping
、HandlerAdapter
等多个策略组件,完美体现了单一职责原则和策略模式。 - 映射驱动:
HandlerMapping
的核心是在启动时建立一个请求(URL)到处理器(方法)的映射Map
,这是实现快速路由的关键。 - 适配器模式的妙用:
HandlerAdapter
隔离了DispatcherServlet
与具体 Controller 方法的调用细节。它内部再使用更细粒度的ArgumentResolver
(参数解析器) 和ReturnValueHandler
(返回值处理器) 来处理具体注解,使得整个体系极易扩展。 - 运行时解析: 所有 Web 注解的解析,都发生在请求到达时,是高度动态的。