浅析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。

image-20210502194235789

在这个自动配置类中,会判断当前应用是否为SERVLET类型的web应用,是的话才会继续导入DispatcherServlet。

image-20210502194532294

当满足上述的条件后,还会再进行其他的判断:

image-20210502195229585

只有DefaultDispatcherServletCondition和ServletRegistration(接口)这两个类存在时才会加载DispatcherServlet,并且通过WebMvcProperties为DispatcherServlet绑定了相应的配置项:

image-20210502195436495

可以看出这个配置是以spring.mvc作为前缀的配置,那么它都包含哪些配置呢:

image-20210502195544975

通过WebMvcProperties类或者application.yml文件我们可以发现它的可配置项极多,这些配置项的作用不是请求映射的关键,这里不再展开讲解。

当满足这一切条件之后,spring boot会为我们new一个dispatcherServlet对象,并进行相应的配置,最后将其放到spring容器中交给容器进行管理。这就是spring的DispatcherServlet的自动装配原理。

DispatcherServlet请求映射

我们可以发现DispatcherServlet是一个Servlet,并且继承自HttpServlet,当我们自己实现HttpServlet类时,通常需要自己实现doGet和doPost方法。

image-20210502200414336

通过源码可以发现,这里HttpServletBean并没有实现doGet和doPost方法,但是在FrameworkServlet中实现了这两个方法:

image-20210502201003633

最终它们调用了processRequest方法进行请求的处理:

image-20210502201059424

这个方法在进行了一系列的初始化操作以后,调用了doService方法,doService方法在FrameworkServlet类中是一个抽象方法,所以这个方法的具体实现是在DispatcherServlet中具体实现的。

image-20210502201308331

在该方法中,除了设置了一系列的属性以外,调用了doDispatch方法进行了请求的分派,这个方法就是请求映射的关键之处。

doDispatch方法

从源码中我们可以发现,doDispatch方法首先通过getHandler方法获取到了处理请求的handler,然后通过调用这个handler来处理前端传递的请求。

image-20210502204441544

调用handler处理前端传递的请求。

image-20210502204609700

那么首先来看看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:

image-20210502205102987

此时可以发现所有的请求都存储在RequestMappingHandlerMapping中:

image-20210502205220958

接着会调用AbstractHandlerMapping#getHandler方法获取HandlerExecutionChain,我们继续深入进去看看它们是如何进行匹配的,通过调试过程中的函数调用我们可以发现匹配发生在getHandlerInternal方法中:

image-20210502213124938

image-20210502211522219

在getHandlerInternal中又调用了getLookupPathForRequest方法获取请求对应的实际路径,通过下图我们可以发现,这个请求的路径信息其实存储在HttpServletRequest中,那么这个函数的作用就是从request中提取请求路径:

image-20210502205733546

接着该方法调用lookupHandlerMethod方法,通过请求路径和HttpServletRequest寻找处理请求的方法:

image-20210502210123481

当找到请求处理的方法以后,会将该方法返回,此时又回到了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

image-20210502214604091

总结

本篇文章重点分析了doDispatch方法调用的getHandler方法,通过该方法获取了HandlerExecutionChain对象,该对象包括处理请求的方法,后续就是通过调用该方法实际处理请求。通过一张图看看本小节的方法调用流程吧:

image-20210502215910724

当我们获取到对应的请求处理方法之后,就可以通过反射的方式调用该方法,执行请求,然后将结果返回给前端。后续文章会继续分析反射调用相关的细节。

以上是关于浅析Spring Boot请求映射原理的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 2从入门到入坟 | 请求参数处理篇:请求映射原理的详细分析

Spring Boot 2从入门到入坟 | 请求参数处理篇:REST映射原理的详细分析

Spring Boot 2从入门到入坟 | 请求参数处理篇:REST映射原理的详细分析

Spring Boot 2从入门到入坟 | 请求参数处理篇:全网最硬核的请求映射原理的源码分析

浅析SpringBoot自动配置的原理及实现

Spring Boot学习笔记总结