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 = Handler(processedRequest);这⼀⾏后,可以看到,系统已经到了对应的处理⽅法
也就是MyController中的test1⽅法。所以getHandler()⽅法就是到请求对应处理⽅法的关键,
3. HandlerMapping
我们在mappedHandler = Handler(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 = MappingsByDirectPath(lookupPath); // 根据请求路径到直接匹配的结果(只根据路径名匹配) if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request); // 从直接匹配的结果中寻并将最终结果存⼊matches中(根据请求⽅法匹配)
}
if (matches.isEmpty()) {
addMatchingMappings(Registrations().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Match bestMatch = (0); // 获取结果集中的第1个值作为最佳匹配
if (matches.size() > 1) { // 如果到了多个匹配的值
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = (0);
if (logger.isTraceEnabled()) {
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = (1); // 获得第⼆最佳匹配
if (comparatorpare(bestMatch, secondBestMatch) == 0) { // 如果最佳匹配和第⼆最佳匹配相同,则抛出异常,说明有两个相同的匹配路径
Method m1 = Method();
Method m2 = Method();
String uri = RequestURI();
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(Registrations().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, Registrations().get(mapping).getHandlerMethod()));
}
}
mvc的controller}
5. 总结
所有的请求映射都保存在HandlerMapping中,在项⽬启动时,SpringMVC会⾃动扫描Controller并解析
注解,将注解信息和处理⽅法保存在HandlerMapping映射处理器中
SpringBoot为我们默认定义并配置了5个HandlerMapping,当⼀个请求进来时,系统会遍历这5个HandlerMapping,到匹配的handler处理⽅法
我们也可以将⾃定义的HandlerMapping放⼊容器中,使⽤⾃定义的映射处理器
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论