本文基于spring 5.5.2.release
上一篇文章《SpringMVC-详解HandlerMapping的前世今生之诞生》简单介绍了HandlerMapping接口,了解了它的实现类可以根据http请求查找Handler,但是没有介绍如何查找,以及Handler是怎么实现的,本文将通过HandlerMapping接口的实现类之一RequestMappingHandlerMapping,来回答上面这些问题。
文章目录
一、创建RequestMappingHandlerMapping对象
先来看一下该类的定义:
/**
* Creates {@link RequestMappingInfo} instances from type and method-level
* {@link RequestMapping @RequestMapping} annotations in
* {@link Controller @Controller} classes.
*/
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
implements MatchableHandlerMapping, EmbeddedValueResolverAware {}
从注释中可以知道该类会创建实例RequestMappingInfo对象,而且与注解@RequestMapping有很大的关系,该类还实现了接口InitializingBean(上面代码没有体现出来),因此springmvc实例化该对象后,会执行afterPropertiesSet()方法做初始化。
下面看一下该类被实例化为对象的过程。
从上一篇文章《SpringMVC-详解HandlerMapping的前世今生之诞生》可以知道RequestMappingHandlerMapping对象是在WebMvcConfigurationSupport类中通过requestMappingHandlerMapping()方法创建的。下面来看一下这个方法:
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
//通过new创建对象
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
//设置order,可以看到RequestMappingHandlerMapping是排序第一的
//也就是DispatcherServlet收到请求遍历HandlerMapping,RequestMappingHandlerMapping是第一个被遍历的
mapping.setOrder(0);
//设置拦截器,这里的拦截器无法通过配置干预
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
//contentNegotiationManager用于控制请求中的media type(媒体类型)
mapping.setContentNegotiationManager(contentNegotiationManager);
//CorsConfiguration用于处理http请求头
mapping.setCorsConfigurations(getCorsConfigurations());
//PathMatchConfigurer是路径匹配的配置对象,
//路径匹配指的是http请求路径与Handler可以处理的路径之间的匹配关系
PathMatchConfigurer configurer = getPathMatchConfigurer();
Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
if (useSuffixPatternMatch != null) {
mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
}
Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
if (useRegisteredSuffixPatternMatch != null) {
mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
}
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
UrlPathHelper pathHelper = configurer.getUrlPathHelper();
if (pathHelper != null) {
mapping.setUrlPathHelper(pathHelper);
}
PathMatcher pathMatcher = configurer.getPathMatcher();
if (pathMatcher != null) {
mapping.setPathMatcher(pathMatcher);
}
Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
if (pathPrefixes != null) {
mapping.setPathPrefixes(pathPrefixes);
}
//返回创建完毕的对象,springmvc将该对象注册到容器中
return mapping;
}
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
return new RequestMappingHandlerMapping();
}
实例化为对象后,spring容器继续调用该类的afterPropertiesSet()方法:
public void afterPropertiesSet() {
//BuilderConfiguration相当于一个配置容器,将配置选项和与配置相关的对象放到容器中
//这个类里面也没有复杂的逻辑,只是一些简单的get/set方法
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
//代码省略
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
//调用父类的afterPropertiesSet()
super.afterPropertiesSet();
}
//下面是父类的afterPropertiesSet()
public void afterPropertiesSet() {
initHandlerMethods();
}
//该方法是该类初始化过程中最关键的方法
protected void initHandlerMethods() {
//getCandidateBeanNames()从spring容器中获得所有的对象名字
//下面这个循环其实是遍历容器中的所有对象
for (String beanName : getCandidateBeanNames()) {
//跳过名字以“scopedTarget.”开头的对象
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
//打印日志,不详细介绍该方法代码
handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
//获得bean对象的类,也就是Class对象
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
//打印日志,代码省略
}
}
//isHandler()方法是检查该类有没有Controller或者RequestMapping注解
//如果有,则返回true
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
protected void detectHandlerMethods(Object handler) {
//获得bean对象的类,也就是Class对象
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
//如果该类被CGLIB代理了,则找到被代理类
Class<?> userType = ClassUtils.getUserClass(handlerType);
//下面这一行代码将遍历类中的所有方法,
//找到该类中所有@RequestMapping注解的方法,并创建对应的RequestMappingInfo对象
//methods保存了被@RequestMapping注解的方法和RequestMappingInfo对象的对应关系
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
//下面将介绍该方法
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
//抛出异常,代码省略
});
//代码省略
methods.forEach((method, mapping) -> {
//对方法的权限做检查,比如检查方法是否是private的
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//将方法和RequestMappingInfo对象注册到一个容器中,下面介绍该方法
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
afterPropertiesSet()方法的调用链比较长,这里做一下总结,上面的代码总体上逻辑是:
- 创建配置对象BuilderConfiguration,便于访问RequestMappingHandlerMapping中的配置信息;
- 遍历spring容器中每个对象,找到被Controller或者RequestMapping注解的对象;
- 遍历上一步对象中的每个方法,找到@RequestMapping注解的方法,并创建对应的RequestMappingInfo对象;
- 将所有的RequestMappingInfo对象注册到一个容器中。
最后两步是在方法getMappingForMethod()和registerHandlerMethod()中完成的,上面代码没有对这两个方法展开。简单的说RequestMappingInfo对象中包含了可以处理http请求的方法信息(也就是被RequestMapping注解的方法),以及该方法可以处理哪些请求(也就是RequestMapping注解中指定的路径,还有一些自定义条件),将RequestMappingInfo对象注册到容器中后,当DispatcherServlet查找处理该请求的方法时,可以通过容器直接找到RequestMappingInfo对象,进而根据RequestMappingInfo调用处理该请求的方法。
下面将对方法getMappingForMethod()和registerHandlerMethod()展开介绍,如果对这个不感兴趣的可以跳过看下一个小节。
1、getMappingForMethod()
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
//创建RequestMappingInfo对象
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
//下面方法是用于找到类上的RequestMapping注解信息,将类上的信息与方法上的注解信息合并
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
//获得注解RequestMapping配置信息
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
//createRequestMappingInfo()方法创建RequestMappingInfo对象
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
//可以看到RequestMappingInfo对象就是根据@RequestMapping的注解信息创建的
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
if (customCondition != null) {
builder.customCondition(customCondition);
}
return builder.options(this.config).build();
}
getMappingForMethod()总起来说是根据@RequestMapping注解创建RequestMappingInfo对象,如果类上也有该注解,那么将类上的与方法上的做融合。
2、registerHandlerMethod()
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
super.registerHandlerMethod(handler, method, mapping);
//根据注解RequestBody的属性required更新自定义条件
//如果设置required=true,那么会将该条件加入到RequestMappingInfo对象的条件集合中
//这样便可以对请求做校验
updateConsumesCondition(mapping, method);
}
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
//mappingRegistry是MappingRegistry的实例,该类中有多个Map属性,
//注册就是将方法信息和注解信息添加到这些Map属性中
this.mappingRegistry.register(mapping, handler, method);
}
在执行mappingRegistry的注册方法时,还会创建HandlerMethod对象,之前一直提到HandlerMapping作用是查找Handler,在RequestMappingHandlerMapping中,Handler指的就是HandlerMethod对象。创建的过程也很简单,直接调用构造方法进行创建:
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
if (handler instanceof String) {
return new HandlerMethod((String) handler,
obtainApplicationContext().getAutowireCapableBeanFactory(), method);
}
//一般是执行下面这个构造方法
//入参handler是Controller对象,method是可以处理http请求的Method对象
//根据这行代码,可以知道HandlerMethod保存了Controller对象和处理请求的方法信息
//这样根据http请求找到了HandlerMethod对象,也就找到处理该请求的Controller方法
//HandlerMethod构造方法逻辑并不复杂,有兴趣大家可以自行查看
return new HandlerMethod(handler, method);
}
创建完成后,springmvc将它和RequestMappingInfo之间的对应关系存储到一个Map对象中:
//mapping指的是RequestMappingInfo,handlerMethod指的是HandlerMethod
this.mappingLookup.put(mapping, handlerMethod);
到这里,RequestMappingHandlerMapping对象的创建完成了,该对象里面包含了容器中所有的@RequestMapping注解的方法信息。
下面介绍DispatcherServlet如何查找处理当前请求的Handler。
二、查找Handler
上面已经提到Handler指的是HandlerMethod对象,通过上面的代码介绍,其实已经大概知道查找过程。查找的代码也是挺多的,这里不详细对每个方法展开介绍,只介绍RequestMappingHandlerMapping的getHandler(),DispatcherServlet查找handler时,便是直接调用的该方法。
//该方法是在RequestMappingHandlerMapping的父类中实现的
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//下面介绍getHandlerInternal方法的处理流程
//handler是HandlerMethod对象
Object handler = getHandlerInternal(request);
//如果没有找到,返回默认值,对于RequestMappingHandlerMapping来说,默认值是null
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
//使用HandlerExecutionChain做封装,添加一些拦截器
//DispatcherServlet不直接调用Controller的方法,而是通过HandlerExecutionChain调用,
//这样可以在调用前和调用后执行拦截器逻辑
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
//代码删减
if (hasCorsConfigurationSource(handler)) {
//下面的代码用于添加拦截器
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;
}
getHandler()里面调用了getHandlerInternal()方法,所有的查找逻辑都在该方法里面,getHandlerInternal()方法的处理比较多,这里简答介绍一下处理流程:
- 从http请求参数中得到请求路径;
- 首先根据请求路径从mappingRegistry中精确查找RequestMappingInfo对象,如果找不到,那么遍历所有的RequestMappingInfo对象,根据http请求参数和RequestMappingInfo对象中记录的条件(也就是注解RequestMapping中的属性值)看看是否有符合要求的,这时可以做模糊匹配;
- 如果找到多个符合要求的RequestMappingInfo对象,那么对这些对象筛选,从中找到一个匹配度最高的RequestMappingInfo对象,比如请求路径的匹配,/teacher/s1都可以模糊匹配/teacher/*和/teacher/s*,但是后者匹配的更多,那么后者的匹配度也就越高;
- 因为RequestMappingInfo与HandlerMethod存在对应关系,找到了RequestMappingInfo对象,也就找到了HandlerMethod对象,将该对象返回;
三、总结
本文首先介绍了RequestMappingHandlerMapping的创建过程,里面涉及到两个关键类RequestMappingInfo和HandlerMethod,前者保存了@RequestMapping注解的配置信息,后者记录了@RequestMapping注解的方法信息,两者一一对应,前文一直提到的Handler,指的就是HandlerMethod,之后介绍根据请求参数查找HandlerMethod对象的过程,找到了HandlerMethod对象,也就找到了处理当前请求的方法,之后便可以调用该方法处理请求了。
从HandlerMethod的构造方法可以知道springmvc的Controller对象是单例的,不是每次请求都新建对象。