SpringMVC请求映射原理
当我们每次发送请求时,系统是如何找到对应的方法来处理请求的呢?为了解决这个问题,我们查看SpringMVC的底层源代码
环境:SpringBoot 2.4.2
1. DispatcherServlet
SpringBoot底层还是使用的SpringMVC,所以请求过来时,都会到达DispatcherServlet
,而DispatcherServlet
继承于FrameworkServlet
,FrameworkServlet
继承于HttpServletBean
,HttpServletBean
继承于HttpServlet
,所以本质上DispatcherServlet
是一个Servlet。那么,在这些类中就要实现doGet()
或者doPost()
方法。我们看到,在HttpServletBean
这个类中并没有实现doGet后者doPost方法,那么我们查看FrameworkServlet
类代码
可以看到四个方法都覆盖了HttpServlet
中对应的方法,且实现都是processRequest()
方法
在这个方法中,关键是调用了doService()
方法,我们查看这个方法
发现这是一个抽象方法,需要其子类去实现。所以,我们查看FrameworkServlet
的子类DispatcherServlet
中的doService()
方法。在这个方法中,我们省略其余代码,最关键的部分为
关键是调用了doDispatch()
方法,所以,对于每个请求进来,都会调用org.springframework.web.servlet.DispatcherServlet
的doDispatch()
这个方法来处理请求
2. doDispatch()方法
在doDispatch方法处加上断点,开启debug
发送一个原生的get请求,在idea中可以看到发送请求的路径为/test
当我们单步调试到mappedHandler = this.getHandler(processedRequest);
这一行后,可以看到,系统已经找到了对应的处理方法
也就是MyController中的test1方法。所以getHandler()
方法就是找到请求对应处理方法的关键,
3. HandlerMapping
我们在mappedHandler = this.getHandler(processedRequest);
这一行加上断点,重新启动debug,重新发送这个get请求,调试进入getHander()方法
在方法的首行,获取了handlerMappings
,这是处理器映射,SpringMVC根据处理器映射里面的映射规则找到对应的处理方法。默认有5个HandlerMapping
处理器映射内部保存着相应的映射规则,我们可以查看熟悉的WelcomePageHandlerMapping
,在里面可以找到pathMatcher属性,这是匹配的请求路径
还可以找到对应的跳转,会转发到index页面,也就是首页
所以这就是欢迎页的处理器映射
对于普通的请求,我们需要注意的是RequestMappingHandlerMapping
,这其中保存了所有@RequestMapping
注解和handler的映射规则,在SpringBoot启动时,SpringMVC会自动扫描Controller并解析注解,将注解信息和处理方法保存在这个映射处理器中。可以将这个HandlerMapping理解为一个Map,其中key为请求路径,value为handler的处理方法
所以在getHandler()
方法中,存在一个for循环,为了找到能处理对应请求的HandlerMapping
进入循环中,当获取到RequestMappingHandlerMapping
时,我们查看这个类中的属性值
可以看到所有标注了@RequestMapping
注解的请求映射规则都已经存放在了这个RequestMappingHandlerMapping
类中
同时,已经获取到了相应的handler,也就是对应的Controller处理方法
4. HandlerMapping的getHandler()方法
读到这里,对于SpringMVC的请求映射原理已经大概熟悉了,接下来,我们分析一下HandlerMapping的getHandler方法,理解是如何根据请求路径从HandlerMapping中找到相应的handler方法的
我们从断点处不断进入,直到进入org.springframework.web.servlet.handler.AbstractHandlerMethodMapping
的lookupHandlerMethod()
方法,我们可以重点分析一下这段代码
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>(); // 存储匹配到的结果
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath); // 根据请求路径找到直接匹配的结果(只根据路径名匹配)
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request); // 从直接匹配的结果中寻找并将最终结果存入matches中(根据请求方法匹配)
}
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0); // 获取结果集中的第1个值作为最佳匹配
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.getRegistrations().keySet(), lookupPath, request);
}
}
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
T match = getMatchingMapping(mapping, request);
if (match != null) {
matches.add(new Match(match, this.mappingRegistry.getRegistrations().get(mapping).getHandlerMethod()));
}
}
}
5. 总结
所有的请求映射都保存在HandlerMapping
中,在项目启动时,SpringMVC会自动扫描Controller并解析注解,将注解信息和处理方法保存在HandlerMapping映射处理器中
SpringBoot为我们默认定义并配置了5个HandlerMapping,当一个请求进来时,系统会遍历这5个HandlerMapping,找到匹配的handler处理方法
我们也可以将自定义的HandlerMapping放入容器中,使用自定义的映射处理器