spring mvc的RequestMappingHandlerMapping注册HandlerMethod源码分析
Posted 不识君的荒漠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring mvc的RequestMappingHandlerMapping注册HandlerMethod源码分析相关的知识,希望对你有一定的参考价值。
前言
先解释下会用到的一些词语/术语的含义:
Handler : 绑定了注解@RequestMapping和@Controller的类
HandlerMethod:就是Handler下某个绑定@RequestMapping注解的方法(GetMapping、PostMapping...等都绑定的有注解@RequestMapping,spring mvc在做注解解析处理生成代理对象等的时候,会做值的合并等处理,所以最终都是用RequestMapping的注解来计算,所以@Controller和@RestController的处理等同)
为什么要注册HandlerMethod,在spring mvc请求流程图--大纲这篇文章中,大体画了个简单的spring mvc的请求处理流程图,其中有一个环节是匹配HandlerMethod的过程,这个匹配过程后面的文章中说明,本文分析下它的初始化过程:如何根据注解信息和对应方法解析绑定在一起的,如下一段代码:
@RestController
@RequestMapping("/demo")
public class DemoController
@RequestMapping(path = "/hello", method = RequestMethod.GET)
public String hello()
return "hello, world";
spring mvc默认只有一个servlet映射/*请求,但是他可以根据请求信息:比如url:/demo/hello等匹配到hello()这个方法,这个过程是它的匹配HandlerMethod的过程,我们本文重点说的这个根据注解信息注册请求映射信息和对应的HandlerMethod的过程。
接下来开始:
RequestMappingHandlerMapping
RequestMappingHandlerMapping这个类,是负责根据注解方式来处理HandlerMethod,那就来看下它的注册过程。
看下这个类的继承关系:
父类只画到了
AbstractHandlerMethodMapping<RequestMappingInfo>
这一级,其中的泛型信息也很重要。
AbstractHandlerMethodMapping这个类实现了接口InitializingBean(不清楚这个接口作用的同学,可以查下资料了解下),所以HandlerMethod的注册便在这个接口实现方法afterPropertiesSet()方法上了(这是因为spring 初始化bean的时候,如果某个bean实现了接口InitializingBean便会回调它的afterPropertiesSet()。
我们看下这个源码:(后面的代码中我都会加上中文注释)
/**
* 在初始化的时候侦测所有的HandlerMethod并注册
*/
@Override
public void afterPropertiesSet()
initHandlerMethods();
接下来进入initHandlerMethods(),
//扫描容器中所有的bean,侦测并注册HandlerMethod
protected void initHandlerMethods()
if (logger.isDebugEnabled())
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
// detectHandlerMethodsInAncestorContexts这个值 默认为true,
// 这样一来就会获取所有的bean名称,然后遍历进行过滤
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames)
//不能是作用域代理类的bean
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX))
Class<?> beanType = null;
try
beanType = getApplicationContext().getType(beanName);
catch (Throwable ex)
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled())
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
//isHandler的判断条件就是类上是否有注解(Controller || RequestMapping)
if (beanType != null && isHandler(beanType))
// 这里就是把每个类下所有符合条件的方法作为HandlerMethod注册,用于匹配时快速查找匹配
detectHandlerMethods(beanName);
// 其实这个默认是个空实现,如果有自己的业务需要可以定制
handlerMethodsInitialized(getHandlerMethods());
接下来进入重点:detectHandlerMethods()这个方法,看下如何注册,
protected void detectHandlerMethods(final Object handler)
// 看上面的代码,明明是传入的字符串,为什么还要判断它的类型,讲真,这里我也没想到用意
Class<?> handlerType = (handler instanceof String ?
getApplicationContext().getType((String) handler) : handler.getClass());
// 获取它的原生类型,比如是个代理类的时候,就获得被代理的类型
final Class<?> userType = ClassUtils.getUserClass(handlerType);
//这个操作是把所有符合条件的method过滤出来, 这个泛型T:RequestMappingInfo
// 这个过滤方法的条件就是看方法上是否使用了注解RequestMapping,注意对RequestMapping
// 注解的解析并不是很简单的直接获取,因为可能使用了GetMapping等,所以会对它继承的注解解析并将属性值拼接起来
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
new MethodIntrospector.MetadataLookup<T>()
@Override
public T inspect(Method method)
try
// getMappingForMethod这个方法会根据method创建对应的请求映射信息返回,后面会重点说下这个方法
return getMappingForMethod(method, userType);
catch (Throwable ex)
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
);
if (logger.isDebugEnabled())
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
// 遍历过滤出来的所有Method和请求映射信息
for (Map.Entry<Method, T> entry : methods.entrySet())
// 这里返回的是目标类型可调用的方法
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
// 开妈注册了
registerHandlerMethod(handler, invocableMethod, mapping);
接下来,应该说registerHadnlerMethod方法,但是这里先解释下getMappingForMethod()这个方法,这是根据方法创建RequestMappingInfo信息,这个可是后面请求到来时匹配HandlerMethod的重要信息:
先看下getMappingForMethod()源码(这个代码是RequetMappingHandlerMapping类实现的):
@Override
//RequestMappingInfo 可是根据注解RequestMapping创建的,所以方法上必须得有RequestMapping注解
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType)
// 这个创建方法可以看下面两个方法,代码比较直观易懂
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null)
// 这个类型信息,是解析类上的RequestMapping注解信息,然后跟方法的进行拼接组合
// 比如方法上有路径/hello,类上:/demo,就可以拼接出/demo/hello,这里的拼接还包含其它信息的拼接,
//如果类上有两条路径,方法有两条,就会产生2*2=4条,多条路径类似
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null)
// 注意这个方法的使用,是谁组合谁,谁在前
info = typeInfo.combine(info);
return info;
// 看下下面两个方法的代码,可以看到创建RequestMappingInfo的具体过程,RequestMappingInfo包含了很多信息,
// 路径、方法、参数...等等,所以匹配是一个复杂的过程,因为条件多,所以匹配也是可能匹配到多个,但是会选择最合适的那个
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element)
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);
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, RequestCondition<?> customCondition)
return RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name())
.customCondition(customCondition)
.options(this.config)
.build();
接下来进入registerHandlerMethod()方法:
// 注册HandlerMethod和它唯一映射信息
protected void registerHandlerMethod(Object handler, Method method, T mapping)
// mappingRegistry是它的一个内部类的实例
this.mappingRegistry.register(mapping, handler, method);
接下来,进入mappingRegistry.register()的源码实现,这也算是到了最后一步:
public void register(T mapping, Object handler, Method method)
this.readWriteLock.writeLock().lock();
try
// 这里会创建一个HandlerMethod实例,HandlerMethod的介绍后期会单独抽出一篇介绍
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
// 如果mappingLookup已经存在mapping信息这里会抛出异常
assertUniqueMethodMapping(handlerMethod, mapping);
if (logger.isInfoEnabled())
logger.info("Mapped \\"" + mapping + "\\" onto " + handlerMethod);
// 这是一个map,保存了Key: RequestMappingInfo和Value: HandlerMethod
this.mappingLookup.put(mapping, handlerMethod);
// 一个mapping是允许存在多条路径的
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls)
//urlLookup是个LinkedMultiValueMap,也就是说一个路径可以有多个mapping
// 在进行匹配的时候,就是先根据url找到合适的mapping,然后根据找到的mapping再去找到HandlerMethod
this.urlLookup.add(url, mapping);
String name = null;
if (getNamingStrategy() != null)
// 大写类名#方法名
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
//CrossOrigin这个跨域注解请求的初始化配置
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null)
this.corsLookup.put(handlerMethod, corsConfig);
this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
finally
this.readWriteLock.writeLock().unlock();
这样,注册过程就完了,后面有时间的话会单写篇文章来分析一下匹配的过程。
以上是关于spring mvc的RequestMappingHandlerMapping注册HandlerMethod源码分析的主要内容,如果未能解决你的问题,请参考以下文章
Spring Security + MVC:相同的@RequestMapping,不同的@Secured
Spring MVC 基础注解之@RequestMapping@Controller
Spring MVC - 02 RequestMapping映射请求