SpringBoot揭秘:URL与HTTP方法如何定位到Controller
1. 引言
相信作为一名程序员,无论是前端还是后端,都曾遇到过计算机网络领域的经典面试题:“当用户在浏览器中输入一个URL会发生什么?”
大多数人对这一问题的回答可能已经烂熟于心:从URL解析、DNS域名解析,到三次握手建立TCP连接,发送HTTP请求,服务器处理请求并返回响应,最后四次挥手关闭连接、页面渲染——整个流程清晰而流畅。
具体问题解答可以参考我的博客: 计算机网络经典面试题
然而,对于后端工程师来说,还有一道更具挑战性的经典问题:“当用户在浏览器中输入一个URL,后端框架(如Spring Boot)是如何精准定位到具体的Controller方法的?”
你或许每天都在使用@Controller
、@RestController
、@RequestMapping
等注解,也熟悉它们的功能,但你是否想过,这些看似简单的注解背后,隐藏着怎样复杂的逻辑?Spring Boot究竟是如何从千变万化的URL和HTTP方法中,迅速找到唯一匹配的处理方法?
如果你曾对这些问题感到好奇,那么恭喜你!这篇文章将以Spring Boot为例,为你揭开“幕后故事”:
- URL和HTTP方法的匹配逻辑:Spring如何优雅地解析复杂路径和方法?
- 底层源码剖析:
DispatcherServlet
到底在忙些什么? - 高效请求分发的秘诀:你写的Controller为什么能如此“聪明”?
2.HTTP请求处理流程
在 Spring Boot 中,每个 HTTP 请求的背后,都隐藏着一条严密而高效的执行路径。从请求进入到最终获得响应,框架内部经历了五个关键环节:DispatcherServlet
、HandlerMapping
、HandlerAdapter
、Handler
(Controller)和执行业务逻辑。我将结合一个流程图,为你详细解析这些环节是如何协作的。
1. DispatcherServlet
DispatcherServlet
是 Spring MVC 的核心前端控制器。所有进入应用的 HTTP 请求,都会首先被它拦截。
其主要职责是分发请求,将其交由合适的处理器(Handler)处理,并最终生成响应。
- 初始化阶段:在应用启动时,
DispatcherServlet
会加载所有的配置文件和组件,例如HandlerMapping
和HandlerAdapter
。 - 请求分发:当请求到达时,它会根据内部组件的协作结果,将请求委派给相应的处理器。
2. HandlerMapping
HandlerMapping
的职责是根据请求的 URL 和 HTTP 方法,找到能够处理该请求的处理器(Handler)。
它内部维护了一系列的映射规则,例如路径匹配规则(@RequestMapping
注解配置的路径)。
- 核心逻辑:通过遍历所有注册的处理器,找到匹配当前请求的那个处理器,并将其封装为
HandlerExecutionChain
,而HandlerExecutionChain用于封装一个处理器(Handler)和与该处理器相关联的拦截器(Interceptors)。 - 扩展点:支持自定义路径匹配策略,如正则表达式或路径模板。
3. HandlerAdapter
HandlerAdapter
的职责是适配处理器,让框架能够灵活支持多种类型的处理器。例如,普通的@Controller
或RestController
,都需要不同的适配方式。
- 核心逻辑:根据
HandlerExecutionChain
提供的处理器类型,选择对应的HandlerAdapter
来执行处理器逻辑。 - 扩展点:可以自定义
HandlerAdapter
,以支持特殊类型的处理器。
4. Handler(Controller)
当请求到达具体的Controller
时,业务逻辑开始被执行。
这里的处理器通常是带有@Controller
或@RestController
注解的类。
- 参数解析:Spring 会通过
HandlerMethodArgumentResolver
为方法中的参数自动赋值(例如,从请求体或 URL 中提取参数)。 - 方法执行:执行具体的业务逻辑,并返回
ModelAndView
(传统 MVC 模式)或直接返回 JSON 数据。
5. 生成响应:结果返回客户端
执行完业务逻辑后,DispatcherServlet
将负责处理返回的结果,并生成最终的 HTTP 响应。
- 视图解析(传统 MVC):若返回的是
ModelAndView
,则通过ViewResolver
解析为 HTML 页面。 - 直接响应:对于 RESTful 风格的 API,结果通常直接返回 JSON 格式的数据。
具体整体的业务处理流程如下所示:
3.DispatcherServlet源码解析
在 Spring MVC 的核心组件 DispatcherServlet 中,doDispatch 方法是其处理 HTTP 请求的核心方法。它实现了从请求到响应的整个控制流程,包括请求解析、处理器分发、业务逻辑执行以及结果返回。
有关doDispatch的源码如下所示:
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
@SuppressWarnings("deprecation")
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
具体的源码分析如下:
1.初始化变量
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
processedRequest
:表示当前处理的请求,初始化为原始请求request
。mappedHandler
:存储找到的处理器链(HandlerExecutionChain
)。multipartRequestParsed
:标记当前请求是否是多部分请求。asyncManager
:管理异步请求的状态。
2.检测和解析多部分请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
HTTP 协议中的普通请求通常是表单数据或 JSON 数据,而多部分请求(Multipart Request)通常是用来处理上传文件的请求,内容会以分段的形式组织。
在 SpringBoot中,MultipartResolver 接口负责解析这种请求。
- 调用
checkMultipart
方法检测并解析多部分请求。 - 如果请求被解析为
MultipartHttpServletRequest
,则processedRequest
会被替换,并标记multipartRequestParsed = true
。
3.查找请求处理器
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
- 调用
getHandler
方法,根据当前请求查找对应的处理器。 - 如果找不到处理器,调用
noHandlerFound
方法返回 404 响应。
4.获取处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
- 根据找到的处理器获取对应的处理器适配器(
HandlerAdapter
)。 - Spring MVC 中有多种处理器(如
Controller
、HttpRequestHandler
等),处理器适配器负责适配不同类型的处理器。
5.检查 Last-Modified缓存
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
- 如果请求是
GET
或HEAD
,检查处理器是否支持Last-Modified
。 - 如果内容未修改,直接返回 304 响应,不需要重复执行控制器方法处理请求。
这一段代码的主要目的是实现 HTTP 的缓存验证机制:
- 优化请求处理:如果资源未修改,直接返回 304 Not Modified,避免重复生成内容。
- 节省带宽:客户端可以直接使用缓存的内容,而不需要重新下载。
- 提高性能:减少服务器的处理开销。
这里插入介绍一下HTTP各种请求方法的作用及场景:
HTTP方法 | 作用 | 常见场景 |
---|---|---|
GET | 获取资源 | 数据查询、页面访问 |
POST | 创建资源或提交数据 | 表单提交、文件上传 |
HEAD | 获取响应头数据 | 检查资源、验证缓存 |
PUT | 创建或替换资源 | 更新资源 |
PATCH | 局部更新资源 | 更新部分字段 |
DELETE | 删除资源 | 删除用户、文件等 |
OPTIONS | 检查支持的HTTP方法 | 调式CORS |
TRACE | 回显请求,用于调试 | 调试 HTTP 请求路径(禁用) |
6.执行处理器前置拦截器
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
- 执行所有拦截器的
preHandle
方法。 - 如果任意一个拦截器返回
false
,请求处理链会中断。
7.调用处理器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
- 调用
HandlerAdapter
的handle
方法,执行实际的请求处理逻辑。 - 如果处理器启动了异步处理,立即返回,等待异步处理完成。
8.应用默认视图名并执行后置拦截器
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
- 如果返回的
ModelAndView
没有指定视图名称,应用默认视图名称。 - 执行所有拦截器的
postHandle
方法。
9.异常处理
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
- 捕获请求处理过程中的异常,交由
processDispatchResult
统一处理。
10.清理资源
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else {
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
doDispatch
方法的核心业务逻辑可以分为以下几个阶段:
- 请求解析:解析多部分请求。
- 处理器查找:根据请求找到对应的处理器和拦截器链。
- 请求处理:执行拦截器前置逻辑、调用处理器、执行拦截器后置逻辑。
- 异常处理:捕获并处理请求处理过程中的异常。
- 清理阶段:清理多部分请求资源或异步请求资源。