在本专栏前面文章中对 SpringMVC
请求执行过程进行了分析,文章地址如下:
SpringMVC 源码解析 - 请求执行的过程
其中有个重要的组件 HandlerMapping
在源码中起到了非常重要的位置,这里的 Handler
也就是我们常见的 Controller
,那 Controller
是如何被识别注册以及查找,本篇文章带领大家一起从源码的角度分析下。
在 HandlerMapping
子类中有个RequestMappingHandlerMapping
,其作用是在容器启动后将系统中所有控制器方法的请求条件和控制器方法的对应关系注册到 RequestMappingHandlerMapping
子类 MappingRegistry
的容器中。当有请求进来时,RequestMappingHandlerMapping
会根据请求条件和容器中存储的系统接口信息比对,再执行对应的控制器方法,从而帮助 DispatcherServlet
分发请求。
下面是 RequestMappingHandlerMapping
继承树关系:
从树中可以看到 RequestMappingHandlerMapping
有实现 InitializingBean
因此在bean
加载完成后会触发afterPropertiesSet
方法:
在该方法下做了 config
配置的初始化,主要逻辑则交由父类的 afterPropertiesSet()
方法进行实现。
下面进到 AbstractHandlerMethodMapping
下的 afterPropertiesSet()
方法中:
这里触发了 initHandlerMethods()
方法,从这里开始核心的逻辑,下面是该方法的源码:
protected void initHandlerMethods() {
//获取候选的bean名称数组
for (String beanName : getCandidateBeanNames()) {
//用于过滤掉以"scopedTarget."开头的bean名称的。
//这些bean名称是由Spring创建的代理对象,用于支持不同作用域的bean
//例如session或request。这些代理对象不是真正的handler,所以要排除掉。
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
//处理候选的bean,判断候选的bean是否是一个handler,也就是是否有@Controller注解或者@RequestMapping注解
//如果是的话,就调用detectHandlerMethods方法,用于检测和注册handler方法
processCandidateBean(beanName);
}
}
//初始化handler方法, 对所有handler方法进行排序和日志输出
handlerMethodsInitialized(getHandlerMethods());
}
在 initHandlerMethods()
中首先排除掉以 scopedTarget
开头的类,因为这些是 Spring
创建的代理对象,下面主要逻辑会交由 processCandidateBean(String beanName)
方法:
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
// 从IOC工厂中获取当前 beanName 的 Class
beanType = obtainApplicationContext().getType(beanName);
} catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
// 如果 beanType 不为空,并且是 handler
if (beanType != null && isHandler(beanType)) {
//检测和注册handler方法
detectHandlerMethods(beanName);
}
}
这里会根据 beanName
从 IOC
工厂中获取到 Class
类型,下面使用 isHandler(Class> beanType)
方法判断是否为 handler
,其实就是判断类上是否带有 Controller
注解 或者 RequestMapping
注解,源码如下:
protected boolean isHandler(Class<?> beanType) {
// 类上是否带有 Controller 注解 或者 RequestMapping 注解
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
如果是 handler
则触发 detectHandlerMethods(Object handler)
方法,进行检测和注册 handler
:
protected void detectHandlerMethods(Object handler) {
//如果handler是一个字符串,那就从IOC工厂中获取到对应的Class,否则就直接使用 handler.getClass()方法来获取类类型。
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
// 如果handler类型不为空
if (handlerType != null) {
// 去除代理和增强后的原始类类型,主要为了避免AOP对方法检测造成干扰。
Class<?> userType = ClassUtils.getUserClass(handlerType);
// 存储userType中所有带有映射注解(比如@RequestMapping) 的方法及其对应的映射信息
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
//获取每个方法上定义或继承的映射信息
return getMappingForMethod(method, userType);
} catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
//遍历methods中,每个方法及其映射信息)
methods.forEach((method, mapping) -> {
//存储经过 AopUtils.selectInvocableMethod() 方法处理后可以被调用(即没有被final修饰)
//且与 userType 匹配(即没有被覆盖) 的原始或桥接(即泛型擦除后生成) 方法
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//注册到处理器映射
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
从上个方法可以看出,该方法接收的 handler
就是 beanName
,因此第一步会再去工厂中获取 Class
类型,下面看下 getMappingForMethod(Method method, Class> handlerType)
方法,主要实现了对注解中的参数信息进行提取:
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
//获取方法上定义或继承的@RequestMapping注解,并将其转换为一个RequestMappingInfo对象
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
//获取类上定义或继承的 @RequestMapping 注解,并将其转换为一个RequestMappingInfo对象
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
//合并两个RequestMappingInfo对象
info = typeInfo.combine(info);
}
//获取处理器类上定义或继承的 @PathPrefix 注解,并将其转换为一个字符串前缀
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
//根据指定的路径前缀和配置选项来构建一个新的请求映射信息,并且与原有信息进行合并
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}
这里分别对方法上和类上的注解的参数进行了提取,将参数信息封装为 RequestMappingInfo
进行返回,其中方法上和类上的属性解析都由 createRequestMappingInfo
方法完成:
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
// 获取 RequestMapping 注解
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
这里获取到 RequestMapping
注解信息后,交由 createRequestMappingInfo
方法封装成一个 RequestMappingInfo
对象:
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
// 解析 RequestMapping 注解中的属性,并生成一个 RequestMappingInfo 对象
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();
}
在 createRequestMappingInfo
方法中就比较明了了,分别取出 RequestMapping
中的不同属性值,填充至 RequestMappingInfo
对象中。
下面再回到detectHandlerMethods(Object handler)
方法中,会通过 MethodIntrospector.selectMethods
方法将 RequestMappingInfo
封装成一个 key
为 Method
,value
为 RequestMappingInfo
对象的 Map
:
在 detectHandlerMethods(Object handler)
方法中最后会遍历上面生成的 Map
,触发 registerHandlerMethod(Object handler, Method method, T mapping)
方法进行 handler
的注册工作:
在 RequestMappingHandlerMapping
中的 registerHandlerMethod
方法同样调用了父类的 registerHandlerMethod
方法,这里来到 AbstractHandlerMethodMapping
类下的 registerHandlerMethod
中:
又调用了mappingRegistry.register
方法,这里则是注册 handler
的主要逻辑,源码如下:
public void register(T mapping, Object handler, Method method) {
// Assert that the handler method is not a suspending one.
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
Class<?>[] types = method.getParameterTypes();
if ((types.length > 0) && "kotlin.coroutines.Continuation".equals(types[types.length - 1].getName())) {
throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
}
}
this.readWriteLock.writeLock().lock();
try {
// 创建 HandlerMethod 对象
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
//获取匹配条件对应的直接路径,添加到pathLookup中
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
// 更新 pathLookup 查找表
for (String path : directPaths) {
this.pathLookup.add(path, mapping);
}
// 如果有命名策略,获取 handler 方法的名称,添加到nameLookup中
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
// 注册新的 HandlerMethod
addMappingName(name, handlerMethod);
}
CorsConfiguration config = initCorsConfiguration(handler, method, mapping);
if (config != null) {
config.validateAllowCredentials();
this.corsLookup.put(handlerMethod, config);
}
// 将匹配条件和MappingRegistration对象(封装了handler方法、直接路径、名称、跨域配置等信息)添加到registry中
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directPaths, name));
} finally {
this.readWriteLock.writeLock().unlock();
}
}
这里首先通过 createHandlerMethod
方法创建了一个 HandlerMethod
对象,源码如下:
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
if (handler instanceof String) {
return new HandlerMethod((String) handler,
obtainApplicationContext().getAutowireCapableBeanFactory(), method);
}
return new HandlerMethod(handler, method);
}
这里的 handler
参数就是前面方法一直传递过来的,因此还是 beanName
,所以会进入到第一个判断中,将 beanName
、IOC
工厂、Method 放入 HandlerMethod
中。
其中 beanName
给了 HandlerMethod
中 bean
属性:
再回到 mappingRegistry.register
方法中,创建完 HandlerMethod
后,将 url
的路径和前面生成的 RequestMappingInfo
对象建立映射关系放入 pathLookup
容器中,在最后又将 RequestMappingInfo
和 MappingRegistration
对象实例建立映射关系放入 registry
中缓存:
DispatcherServlet
下获取 Handler
在前面 SpringMVC
源码梳理的文章中就分析过,但那次分析的确实不怎么详细,因此这里再继续梳理下,看是如何和上面注册的过程形成呼应的。
DispatcherServlet
下的 getHandler
中会循环所有的 HandlerMapping
类,使用 mapping.getHandler
方法尝试获取一个 HandlerExecutionChain
对象,如果有则提前终止,HandlerExecutionChain
对象主要就是对 HandlerMethod
和 拦截器进行再次包装。
这里 mapping.getHandler
方法我们看到 AbstractHandlerMapping
下的 getHandler
方法中:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 模板方法,留给子类实现
Object handler = getHandlerInternal(request);
if (handler == null) {
//handler为空获取默认的处理器 默认的 handler 也为 null
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// handler 如果是字符串对象,则通过 Spring 容器尝试获取 Handler 实例
if (handler instanceof String) {
String handlerName = (String) handler;
// 获取 bean 实例
handler = obtainApplicationContext().getBean(handlerName);
}
//获取 HandlerExecutionChain 对象 ,包含 handler 和对应的 Interceptor
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)) {
// cors 的检查请求
CorsConfiguration config = getCorsConfiguration(handler, request);
if (getCorsConfigurationSource() != null) {
CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
config = (globalConfig != null ? globalConfig.combine(config) : config);
}
if (config != null) {
config.validateAllowCredentials();
}
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
在这里首先使用 getHandlerInternal(HttpServletRequest request)
方法获取一个 handler
,其实就是 HandlerMethod
对象,进到该方法中:
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 获取请求路径
String lookupPath = initLookupPath(request);
// 映射存储器,尝试获取读锁
this.mappingRegistry.acquireReadLock();
try {
// 通过路径和请求找到 HandlerMethod
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
// 如果找到则创建一个新的 HandlerMethod , 主要将String类型的Bean更改为容器中实际的bean
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
} finally {
// 放行锁
this.mappingRegistry.releaseReadLock();
}
}
这里首先拿到请求的路径,主要是去掉上下文路径和后缀名的URL
路径,接着尝试获取映射存储器的读锁,主要防止有写的情况造成线程安全问题,拿到锁后通过 lookupHandlerMethod
方法匹配一个合适的 HandlerMethod
,下面看到 lookupHandlerMethod
方法中:
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// 根据 loopuPath 获取对应 RequestInfoMapping 对象集合
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
// 将找到的 RequestInfoMapping 添加到容器缓存
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// 如果没找到,则将所有匹配条件的添加到容器缓存
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
if (!matches.isEmpty()) {
// 获取容器中的第一个
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
// 数量大于1的话进行排序后取第一个 HandlerMethod 包装类,请求条件数量多的优先
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 + "}");
}
}
//设置 HandlerMethod 的 request 属性
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
// 路径变量处理
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
} else {
return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
}
}
这里首先根据 lookupPath
通过 mappingRegistry.getMappingsByDirectPath
获取到一个 List
集合,进到mappingRegistry.getMappingsByDirectPath
方法中就会发现就是从上面注册过程中的 pathLookup
容器中获取信息,这里List
也就是 RequestInfoMapping
对象集合:
下面获取到 RequestInfoMapping
对象集合后,继续向下使用 addMatchingMappings
方法匹配出 Match
对象放入 matches
集合中,这里Match
对象其实就是 HandlerMethod
的一个包装类,进入到 addMatchingMappings
方法中:
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,
this.mappingRegistry.getRegistrations().get(mapping).getHandlerMethod()));
}
}
}
在这里遍历了所有的 RequestInfoMapping
通过 getMatchingMapping
方法获取一个匹配的 RequestInfoMapping
对象,如果存在就生成一个 Match
包装对象放入前面的 matches
集合中,下面继续看 getMatchingMapping
方法,该类在 RequestMappingInfoHandlerMapping
类中:
这里直接使用的 RequestInfoMapping
类的 getMatchingCondition
方法,在进到 info.getMatchingCondition
方法中:
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
// 方法匹配
RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
if (methods == null) {
return null;
}
//请求参数条件匹配
ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
if (params == null) {
return null;
}
//请求头匹配
HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
if (headers == null) {
return null;
}
//可消费 MIME 匹配条件
ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
if (consumes == null) {
return null;
}
//可生成 MIME 匹配条件
ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
if (produces == null) {
return null;
}
PathPatternsRequestCondition pathPatterns = null;
//路径匹配,即 url
if (this.pathPatternsCondition != null) {
pathPatterns = this.pathPatternsCondition.getMatchingCondition(request);
if (pathPatterns == null) {
return null;
}
}
PatternsRequestCondition patterns = null;
if (this.patternsCondition != null) {
patterns = this.patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
}
}
RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
if (custom == null) {
return null;
}
return new RequestMappingInfo(
this.name, pathPatterns, patterns, methods, params, headers, consumes, produces, custom);
}
这里经过不同维度的 Condition
进行匹配,如果符合条件的话才认为这个 RequestInfoMapping
匹配成功,最后生成了一个新的 RequestMappingInfo
对象装入了 Condition
后的不同维度的信息。
下面再回到 addMatchingMappings
方法中,上面提到如果匹配到则创建一个 Match
包装对像,源码如下:
前面在注册的过程使用 RequestInfoMapping
和 HandlerMethod
组成映射放入了registry
容器中,这里拿到 RequestInfoMapping
后,就可以通过registry
容器获取到 HandlerMethod
了。
再回到 lookupHandlerMethod
中,addMatchingMappings
方法匹配到到 RequestInfoMapping
也拿到了 HandlerMethod
,正常情况下我们都是一个 url
对应一个接口,如果这里匹配出多个并且一致的话,则会抛出异常,否则最后返回匹配出来的 HandlerMethod
:
下面再回到 getHandlerInternal
方法中,这里拿到 HandlerMethod
后,没有直接选择返回,而是使用 handlerMethod.createWithResolvedBean()
生成了一个新的 HandlerMethod
:
这个原因从上面注册过程就可以看出,缓存下的 HandlerMethod
里的 bean
属性,其实还只是 beanName
,我们要通过反射执行方法的话,是需要目标对象的,因此在 createWithResolvedBean
方法中尝试从 IOC
容器中获取出实例:
下面再回到 AbstractHandlerMapping
类的 getHandler
方法,第一步通过 getHandlerInternal
方法拿到了一个已经有目标实例对象的HandlerMethod
了,在向下则通过 getHandlerExecutionChain
生成 HandlerExecutionChain
类型的包装类,主要再将拦截器加进来,源码如下:
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
//封装 HandlerExecutionChain 对象
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
// 将 adaptedInterceptors 中,MappedInterceptor中符合要求的添加进去
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
// Spring内部维护的一个url匹配拦截器
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(request)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
// 添加拦截器
chain.addInterceptor(interceptor);
}
}
return chain;
}
最后将 HandlerExecutionChain
对象返回就完成了获取 Handler
的工作。