浅析Spring Boot请求映射原理
Posted 风在哪
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅析Spring Boot请求映射原理相关的知识,希望对你有一定的参考价值。
浅析Spring Boot请求映射原理
Spring Boot的底层还是Spring MVC,那么Spring Boot的请求映射就与Spring MVC是相同的,那就是根据DispatcherServlet完成了请求映射的功能。但是我们没有手动去配置DispatcherServlet对应的配置项,那么Spring Boot是如何加载的DispatcherServlet呢,这个答案就是之前讲解的Spring Boot自动装配。
那么首先来看看自动装配的DispatcherServlet
测试程序
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/hello")
public String hello() {
return "hello world!";
}
}
加载DispatcherServlet
之前分析Spring Boot自动装配原理时可以发现,Spring Boot是通过扫描所有引入的jar包中的spring.factories文件来获取自动配置项的,那么DispatcherServlet的自动配置也会通过这种方法引入。我们可以在spring-boot-autoconfigure-2.3.4.RELEASE.jar包下的META-INF/spring.factories中找到这个自动配置类:DispatcherServletAutoConfiguration。
在这个自动配置类中,会判断当前应用是否为SERVLET类型的web应用,是的话才会继续导入DispatcherServlet。
当满足上述的条件后,还会再进行其他的判断:
只有DefaultDispatcherServletCondition和ServletRegistration(接口)这两个类存在时才会加载DispatcherServlet,并且通过WebMvcProperties为DispatcherServlet绑定了相应的配置项:
可以看出这个配置是以spring.mvc作为前缀的配置,那么它都包含哪些配置呢:
通过WebMvcProperties类或者application.yml文件我们可以发现它的可配置项极多,这些配置项的作用不是请求映射的关键,这里不再展开讲解。
当满足这一切条件之后,spring boot会为我们new一个dispatcherServlet对象,并进行相应的配置,最后将其放到spring容器中交给容器进行管理。这就是spring的DispatcherServlet的自动装配原理。
DispatcherServlet请求映射
我们可以发现DispatcherServlet是一个Servlet,并且继承自HttpServlet,当我们自己实现HttpServlet类时,通常需要自己实现doGet和doPost方法。
通过源码可以发现,这里HttpServletBean并没有实现doGet和doPost方法,但是在FrameworkServlet中实现了这两个方法:
最终它们调用了processRequest方法进行请求的处理:
这个方法在进行了一系列的初始化操作以后,调用了doService方法,doService方法在FrameworkServlet类中是一个抽象方法,所以这个方法的具体实现是在DispatcherServlet中具体实现的。
在该方法中,除了设置了一系列的属性以外,调用了doDispatch方法进行了请求的分派,这个方法就是请求映射的关键之处。
doDispatch方法
从源码中我们可以发现,doDispatch方法首先通过getHandler方法获取到了处理请求的handler,然后通过调用这个handler来处理前端传递的请求。
调用handler处理前端传递的请求。
那么首先来看看getHandler方法。
getHandler
可以看出这个方法通过存储在HandlerMapping中的handler选择匹配当前请求的handler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
// org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
此时总共handlerMappings中共包含六个handlerMapping:
此时可以发现所有的请求都存储在RequestMappingHandlerMapping中:
接着会调用AbstractHandlerMapping#getHandler方法获取HandlerExecutionChain,我们继续深入进去看看它们是如何进行匹配的,通过调试过程中的函数调用我们可以发现匹配发生在getHandlerInternal方法中:
在getHandlerInternal中又调用了getLookupPathForRequest方法获取请求对应的实际路径,通过下图我们可以发现,这个请求的路径信息其实存储在HttpServletRequest中,那么这个函数的作用就是从request中提取请求路径:
接着该方法调用lookupHandlerMethod方法,通过请求路径和HttpServletRequest寻找处理请求的方法:
当找到请求处理的方法以后,会将该方法返回,此时又回到了getHandlerInternal方法,接着会将HandlerMethod返回到AbstractHandlerMapping#getHandler方法中。
下面看看各个方法的详解
AbstractHandlerMapping#getHandler方法
getHandler方法主要通过HttpServletRequest对象获取处理该请求的HandlerExecutionChain
在该方法中,会调用getHandlerExecutionChain方法将HandlerMethod对象包装成HandlerExecutionChain对象,并且此时会判断是否又拦截器加载到这个请求上,如果有的话会在chain上面添加拦截器的相关处理方法
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 获取HandlerMethod对象,也就是实际处理请求的方法
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// 判断获取的handler是否为对应的bean名称,如果是的话直接通过getBean方法获取对应的handler
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 将handler包装成HandlerExecutionChain
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
// 查看是否存在跨域配置
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
// 将handler包装成HandlerExecutionChain对象
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
// 通过请求路径判断是否有匹配的拦截器,有的话就将拦截器加载到请求执行链中
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
getHandlerInternal方法
该方法主要通过HttpServletRequest对象获得对应的请求处理方法
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 根据request查找请求路径
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();
}
}
@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匹配的方法
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);
}
}
HandlerMapping何时注册的?
其实HandlerMapping是通过自动装配注入到spring容器中的,通过下图可以发现WebMvcAutoConfiguration这个自动配置类为我们自动装配了RequestMappingHandlerMapping和WelcomepageHandlerMapping这两个HandlerMapping
总结
本篇文章重点分析了doDispatch方法调用的getHandler方法,通过该方法获取了HandlerExecutionChain对象,该对象包括处理请求的方法,后续就是通过调用该方法实际处理请求。通过一张图看看本小节的方法调用流程吧:
当我们获取到对应的请求处理方法之后,就可以通过反射的方式调用该方法,执行请求,然后将结果返回给前端。后续文章会继续分析反射调用相关的细节。
以上是关于浅析Spring Boot请求映射原理的主要内容,如果未能解决你的问题,请参考以下文章
Spring Boot 2从入门到入坟 | 请求参数处理篇:请求映射原理的详细分析
Spring Boot 2从入门到入坟 | 请求参数处理篇:REST映射原理的详细分析
Spring Boot 2从入门到入坟 | 请求参数处理篇:REST映射原理的详细分析