请求映射原理
# 200.请求映射原理
接下来我们讲解请求映射原理,也就是一个请求,具体是怎么找到具体的方法去处理的
# DispatcherServlet
首先,SpringBoot 用的是 SpringMVC;而在 SpringMVC 中,核心组件是 DispatcherServlet,前端控制器。
我们可以打开其源码(在 IDEA 中按两下 shift 键,就会打开搜索框,希望读者自己也跟着动手看源码),然后我们可以分析出其继承关系是这样的:DispatcherServlet
→ FrameworkServlet
→ HttpServletBean
→ HttpServlet
,所以它还是一个 Servlet;
那么它必须重写 doGet 和 doPost 方法,但是不是在 DispatcherServlet
,而是在 FrameworkServlet
重写的。其部分源码如下:
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
可以看到,所有请求方法都是调用 processRequest 方法来处理的,而在该方法中,最主要的代码是:
try {
doService(request, response);
}
2
3
接下来我们看 doService 方法:可以看到其是一个抽象方法。
protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
throws Exception;
2
既然是抽象方法,理所当然就得是子类去实现了,因此我们分析 DispatcherServlet
的 doService 方法,在该方法一开始都是一些初始化的过程,关键还是调用了 doDispatch 方法,该方法就是用来做转发的了:
try {
doDispatch(request, response);
}
2
3
也就是每个请求,都会调用该方法,我们分析映射原理,也是要看该方法
# doDispatch
接下来我们在该方法打个断点,并以 debug 的方式来启动,来逐步分析请求;
然后我们可以将鼠标放到 request 参数上,会有个小弹框:
点击它,就能展开该变量的详细情况了:例如可以看到请求路径是 /user
然后我们往下执行代码(快捷键 F8)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
2
3
4
5
6
前几行代码:
- 首先用一个变量接受了 request 请求,
- 第二行则是一个调用链,后续再说;
- 第 3 行则是判断是否文件上传请求,默认是 false
- 然后是一个异步请求的处理,我们先不管
接下来的代码:
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
一开始几行是初始化的,关键是第 10 行开始,上面也有注释:决定哪个 handler 处理当前请求
我们一步步执行,直到第 10 行,然后点击步入(快捷键 F7)
可以看到,其首先判断 handlerMappings
是否为空(这里不为空,有 5 个值)
# HandlerMapping
HandlerMapping
,就是处理器映射,存储了某个请求该由谁处理的信息,并存储在一个集合里。我们可以看到第 1 个,WelcomePageHandlerMapping,就是欢迎页的处理,可以展开来看,其路径是 /
,并且对应的资源(view)是 index.html:
接下来我们看第 0 个:RequestMappingHandlerMapping,这就是我们使用注解@RequestMapping 后,存储的路径和方法信息都会存到这里:
那么该 handlerMappings
集合的内容是哪来的呢?不言而喻,就是我们项目启动的时候,SpringBoot 帮我们扫描的,然后放到该集合里。
接下来就是 for 循环,从该集合里找到当前请求,具体是哪个方法来处理了。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
2
3
4
5
6
7
8
9
10
11
我们继续往下,直到执行第 4 行的的时候,不如进去,此时可以看到是 AbstractHandlerMapping
类的 getHandler
方法被执行,然后我们执行到第 2 步,继续步入:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
2
3
4
5
6
7
8
然后我们看 getHandlerInternal 方法,可以看到会调用父类的方法:
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
try {
return super.getHandlerInternal(request);
}
finally {
ProducesRequestCondition.clearMediaTypesAttribute(request);
}
}
2
3
4
5
6
7
8
9
10
我们继续步入到父类的方法中:
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
2
3
4
5
6
7
8
9
10
11
12
分析:
- 首先获取 lookupPath,也就是访问的路径
- 第 4 行还获取了一把锁,避免并发的问题
- 然后第 6 行,就是获取具体的方法了,我们步入进去
lookupHandlerMethod 方法源码如下:
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
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
首先,第 4 行先根据 URL 找,找到所有的处理器(因为/user 有很多处理器,只不过 HTTP 请求的方式不同),这里推测是 4 个,debug 的结果也是 4 个:
下一步,就是将所有找到的处理器,添加到集合里:
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
2
3
如果没找到,则添加一些空的元素进去:
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
2
3
4
接下来就是寻找真正匹配的处理器了。首先会判断是否为空,不为空则先获取第一个;
然后如果找到了多个(大于 1),说明找到了多个,那么 if 判断里会先排个序,看看哪个才是真正最佳匹配的(url 相同,请求方式也相同),如果找不到最佳匹配的,说明有多个处理器,都可以处理该请求,则会报错
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
// 省略其他代码
throw new IllegalStateException("Ambiguous handler methods mapped for '"
+ uri + "': {" + m1 + ", " + m2 + "}");
2
3
4
5
6
7
8
# 自动配置的 HandlerMapping
刚刚我们讲过 handlerMappings 存储了几个组件:
这几个是 SpringBoot 自动帮我们配置的,可以看到 WebMvcAutoConfiguration
类中有对应的@Bean:
@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping(....){...}
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(....){...}
2
3
4
5
6
7
8
# 总结
所有的请求映射都在 HandlerMapping 中:
自动配置了欢迎页的 WelcomePageHandlerMapping 。访问
/
,能访问到 index.html;SpringBoot 自动配置了默认了 RequestMappingHandlerMapping
请求进来时,会挨个尝试所有的 HandlerMapping 看是否有请求信息。
- 如果有就找到这个请求对应的 handler
- 如果没有就是下一个 HandlerMapping
如果需要一些自定义的映射处理,也可以自己给容器中放 HandlerMapping(自定义)