前言
我们发出的请求,SpringMVC是如何精准定位到那个Controller以及具体方法?其实这都是 HandlerMapping 发挥的作用,这篇博文我们以 RequestMappingHandlerMapping 为例并结合源码一步步进行分析。
定义HandlerMapping
默认 HandlerMapping 主要定义在 EnableWebMvcConfiguration 和其祖父类 WebMvcConfigurationSupport 中
EnableWebMvcConfiguration
@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
// Must be @Primary for MvcUriComponentsBuilder to work
return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,
resourceUrlProvider);
}
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
return welcomePageHandlerMapping;
}
WebMvcConfigurationSupport
@Bean
public BeanNameUrlHandlerMapping beanNameHandlerMapping(
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
mapping.setOrder(2);
PathMatchConfigurer pathConfig = getPathMatchConfigurer();
if (pathConfig.getPatternParser() != null) {
mapping.setPatternParser(pathConfig.getPatternParser());
}
else {
mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
}
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
mapping.setCorsConfigurations(getCorsConfigurations());
return mapping;
}
@Bean
public RouterFunctionMapping routerFunctionMapping(
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
RouterFunctionMapping mapping = new RouterFunctionMapping();
mapping.setOrder(3);
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
mapping.setCorsConfigurations(getCorsConfigurations());
mapping.setMessageConverters(getMessageConverters());
PathPatternParser patternParser = getPathMatchConfigurer().getPatternParser();
if (patternParser != null) {
mapping.setPatternParser(patternParser);
}
return mapping;
}
@Bean
@Nullable
public HandlerMapping resourceHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
Assert.state(this.applicationContext != null, "No ApplicationContext set");
Assert.state(this.servletContext != null, "No ServletContext set");
PathMatchConfigurer pathConfig = getPathMatchConfigurer();
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
this.servletContext, contentNegotiationManager, pathConfig.getUrlPathHelper());
addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
if (handlerMapping == null) {
return null;
}
if (pathConfig.getPatternParser() != null) {
handlerMapping.setPatternParser(pathConfig.getPatternParser());
}
else {
handlerMapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
handlerMapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
}
handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
handlerMapping.setCorsConfigurations(getCorsConfigurations());
return handlerMapping;
}
PS:WebMvcConfigurationSupport 中定义的 HandlerMapping 不止上述三个,但是有效的只有三个 (SpringBoot 版本 2.6.13),有的 HandlerMapping 需要满足一定条件才生效。
初始化
initHandlerMappings
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
for (HandlerMapping mapping : this.handlerMappings) {
if (mapping.usesPathPatterns()) {
this.parseRequestPath = true;
break;
}
}
}
detectAllHandlerMappings 属性是否为 true (默认为true)
- true:获取 BeanFactory 中类型为 HandlerMapping 的 beans
- false : 获取 BeanFactory 中类型为 HandlerMapping,beanName为 handlerMapping 的 bean
如果 detectAllHandlerMappings 属性为 true,则会对查找到的 beans 进行排序,排序规则如下:
- HandlerMapping 是否继承 PriorityOrdered 接口,如果都继承 PriorityOrdered 接口,比较getOrder方法返回的值,值越小,优先级越高
- HandlerMapping 是否继承 Ordered 接口,如果都继承 Ordered接 口,比较 getOrder 方法返回的值,值越小,优先级越高
- HandlerMapping 所属class上是否存在 @Order 注解,如果存在,比较注解设置的值,值越小,优先级越高
- HandlerMapping 所属class上是否存在 @Priority 注解,如果存在,比较注解设置的值,值越小,优先级越高
如果从 BeanFactory 中未获取到相关 HandlerMapping,则使用默认 HandlerMapping
默认的 HandlerMapping 定义在 DispatcherServlet.properties 文件中
默认HandlerMapping为
- BeanNameUrlHandlerMapping
- RequestMappingHandlerMapping
- RouterFunctionMapping
请求映射
DispatcherServlet#doDispatch
DispatcherServlet#getHandler
一共有五个 HandlerMapping (SpringBoot 版本 2.6.13,最新版本貌似有6个,大家自行验证一下),即我们上文所分析的在类EnableWebMvcConfiguration、WebMvcConfigurationSupport 中定义的 HandlerMapping
如果某个 HandlerMapping 的 getHandler 方法返回了一个有效的 HandlerExecutionChain,我们就可以认为这个 HandlerMapping 可以处理这个请求。接下里以 RequestMappingHandlerMapping 为例进行分析,大部分请求也都是由这个 HandlerMapping 处理的
PS : WelcomePageHandlerMapping 就是处理欢迎页的
RequestMappingHandlerMapping的实例化
RequestMappingHandlerMapping的类继承关系
通过上图,我们知道其祖父类(AbstractHandlerMethodMapping) 继承 InitializingBean 接口,继承 InitializingBean 接口的类会在bean的实例化过程中执行 afterPropertiesSet 方法
AbstractHandlerMethodMapping#afterPropertiesSet
获取所有类型是 Object 的 beans,并且 beanName 不以 scopedTarget. 开头
AbstractHandlerMethodMapping#processCandidateBean
根据 beanName 获取 beanType
AbstractHandlerMethodMapping#isHandler
如果 beanType 上存在 @Controller 、@RequestMapping 注解则进行处理
AbstractHandlerMethodMapping#detectHandlerMethods
主要有三个方法 selectMethods、getMappingForMethod、registerHandlerMethod
- selectMethods :遍历类中定义的方法以及接口实现方法(方法不能是合成的、桥接的,返回类型不能是Object ),如果某个方法执行接口函数(主体是 getMappingForMethod 方法)的返回值不为null,则将其放入一个类型为 Map<Method, T> 的 Map 中
- getMappingForMethod:如果方法存在 @RequestMapping (@GetMapping、@PostMapping、@PutMapping、@DeleteMapping 等)注解,则构建一个 RequestMappingInfo 对象
- registerHandlerMethod : 遍历 selectMethods 方法的返回结果(Map<Method, T>),将其注册到 AbstractHandlerMethodMapping 的 mappingRegistry 属性中
PS : 经过测试,如果存在 @RequestMapping 注解,即使方法的描述符是 private,也可以接受请求
RequestMappingHandlerMapping的getHandler方法
AbstractHandlerMapping#getHandler
RequestMappingInfoHandlerMapping#getHandlerInternal
AbstractHandlerMethodMapping#getHandlerInternal
AbstractHandlerMethodMapping#lookupHandlerMethod
假设有一个UserController,明细由下方所示,我们来看一下这个 mappingRegistry 属性的结构
@RestController
public class UserController {
@GetMapping("/user")
public String getUser() {
return "get user";
}
@PostMapping("/user")
public String postUser() {
return "post user";
}
@PutMapping("/user")
public String putUser() {
return "put user";
}
@DeleteMapping("/user")
public String deleteUser() {
return "delete user";
}
}
URI 和具体方法的映射关系,都存储在 mappingRegistry 这个属性中
扩展:自定义HandlerMapping
创建 CustomHandlerMapping
public class CustomHandlerMapping implements HandlerMapping, PriorityOrdered {
private ApplicationContext applicationContext;
public CustomHandlerMapping(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
String beanName = request.getParameter("beanName");
String methodName = request.getParameter("method");
if (StringUtils.isBlank(beanName) || StringUtils.isBlank(methodName) || !applicationContext.containsBean(beanName)) {
return null;
}
Object object = applicationContext.getBean(beanName);
Method method = null;
Method[] declaredMethods = object.getClass().getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if (declaredMethod.getName().equals(methodName)) {
method = declaredMethod;
break;
}
}
if (method == null) {
return null;
}
HandlerMethod handlerMethod = new HandlerMethod(object, method);
return new HandlerExecutionChain(handlerMethod);
}
@Override
public int getOrder() {
return 0;
}
}
继承 PriorityOrdered 接口,让我们自定义的 HandlerMapping 优先级最高
创建 HandlerMappingConfig
@Configuration
public class HandlerMappingConfig implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Bean
public CustomHandlerMapping customHandlerMapping() {
return new CustomHandlerMapping(applicationContext);
}
}
创建 CustomHandlerMappingController
@Component("chmc")
public class CustomHandlerMappingController {
@ResponseBody
public String hello() {
return "hello HandlerMapping! ";
}
}
发送请求 localhost:8080?beanName=chmc&method=hello
自定义 handlerMapping 生效