一个 @RequestMapping 就能接收请求?Spring MVC 是怎么做到的
上一篇我们讲了:
SpringBoot 怎么知道该创建哪些 Bean?
SpringBoot 启动时会扫描 Bean,注册到 Spring 容器中。
但新的问题来了。
我们平时写接口时,只需要一个注解:
@RestController
public class UserController {
@GetMapping("/user")
public String user() {
return "ok";
}
}
浏览器访问:
http://localhost:8080/user
居然就能直接进入这个方法。
很多人天天写 SpringMVC,却很少思考:
- 请求是谁接收的?
- URL 是什么时候和方法绑定的?
- Spring 为什么知道调用哪个 Controller?
- 这个映射关系存在哪里?
今天我们就把 SpringMVC 最核心的一条链路彻底搞明白。
先思考一个问题
如果让你自己实现 MVC。
你会怎么做?
最简单的方法可能是:
Map<String, Method> mapping = new HashMap<>();
mapping.put("/user", UserController.class.getMethod("user"));
mapping.put("/order", OrderController.class.getMethod("order"));
请求来了:
Method method = mapping.get("/user");
method.invoke(controller);
是不是就能实现?
SpringMVC 的核心思想其实也差不多。
只是做得更复杂、更完善。
Spring 启动时干了什么
SpringBoot 启动完成后。
SpringMVC 会扫描所有 Controller。
例如:
@RestController
public class UserController {
@GetMapping("/user")
public String user() {
return "ok";
}
}
扫描到后会解析:
@GetMapping("/user")
然后生成映射关系:
/user
↓
UserController.user()
最终保存到一个非常重要的组件中:
MappingRegistry
源码中大概是这样的:
Map<RequestMappingInfo, MappingRegistration<?>> registry;
里面保存了所有 URL 和方法的对应关系。
可以理解成:
/user
↓
UserController.user()
/order
↓
OrderController.order()
/login
↓
LoginController.login()
所以启动的时候。
Spring 其实已经把路由表提前建立好了。
请求来了以后发生什么
浏览器发送请求:
GET /user
首先到达:
DispatcherServlet
它是 SpringMVC 的核心入口。
所有请求都会先经过它。
流程大概如下:
浏览器
↓
DispatcherServlet
此时 DispatcherServlet 并不知道该调用哪个 Controller。
于是它会去找:
HandlerMapping
HandlerMapping 在干什么
HandlerMapping 的职责非常简单:
根据请求路径找到对应的方法。
例如:
请求路径:
/user
它会去 MappingRegistry 中查找:
/user
↓
UserController.user()
找到后返回一个对象:
HandlerMethod
HandlerMethod 是什么
很多同学 Debug 时都见过:
HandlerMethod
里面其实保存了两个核心信息:
bean
method
例如:
UserController
user()
可以理解成:
new HandlerMethod(
userController,
userMethod
)
也就是说:
URL
↓
HandlerMethod
↓
Controller方法
Spring 已经知道该执行谁了。
找到方法为什么不直接执行
很多人看到这里会疑惑。
既然已经找到方法了。
为什么不直接:
method.invoke(...)
还要多一个组件?
因为 SpringMVC 还要解决很多问题:
- 参数绑定
- 文件上传
- JSON转换
- 返回值处理
- 异常处理
所以 Spring 又引入了:
HandlerAdapter
执行流程变成:
DispatcherServlet
↓
HandlerMapping
↓
HandlerMethod
↓
HandlerAdapter
↓
执行方法
真正调用 Controller 的其实是 HandlerAdapter。
Debug 一次最容易看懂
在 Controller 打断点:
@GetMapping("/user")
public String user() {
return "ok";
}
访问接口。
调用栈大概会看到:
DispatcherServlet.doDispatch()
↓
RequestMappingHandlerMapping
↓
HandlerMethod
↓
RequestMappingHandlerAdapter
↓
InvocableHandlerMethod
↓
UserController.user()
看到这里。
SpringMVC 的核心流程就彻底清楚了。
整个流程总结
启动阶段:
扫描 Controller
↓
解析 @RequestMapping
↓
生成 RequestMappingInfo
↓
注册到 MappingRegistry
请求阶段:
请求到达
↓
DispatcherServlet
↓
HandlerMapping
↓
找到 HandlerMethod
↓
HandlerAdapter
↓
执行 Controller 方法
↓
返回结果
所以:
@RequestMapping 并不是收到请求时才解析。
而是在 Spring 启动时就已经解析完成,并建立好了 URL 和方法的映射关系。
请求来了以后,本质上只是查表找到对应的方法再执行而已。
最后一个问题
既然 Spring 已经找到 Controller 方法了。
那么下面这个代码:
@GetMapping("/user")
public User user(
@RequestParam Long id,
HttpServletRequest request) {
...
}
参数是谁赋值的?
@RequestParam 为什么能自动接收请求参数?
HttpServletRequest 又是谁传进来的?
下一篇我们继续聊:
DispatcherServlet 为什么是 SpringMVC 的大脑?



