在SpringMVC中,咱们如果须要开启注解,即:咱们常常应用的@RequestMapping,@Controller注解须要失效,通常应用SpringMVC框架提供的标签"<mvc:annotation-driven/>"来实现。那么这个标签是如何来实现注解驱动让注解失效呢?上面来揭晓一下失效过程!

1、标签干了什么事?

首先要晓得标签干了什么事儿,先得找到这个标签咋解析的。对于标签解析,之前文章中有过具体的介绍,能够参考文章:《Spring标签失效可真不容易》,此处咱们间接找到对应的标签解析器AnnotationDrivenBeanDefinitionParser这个类,而后找到类中的parse办法,外围操作就是通过new的形式创立了几个BeanDefinition,后放到容器中。其中就有一个比拟重要的Definition,名称为RequestMappingHandlerMapping。当然了,还有其余几个BeanDefinition,别着急,咱们前面再介绍。

总结: 此处标签能够暂且认为干了一件事,就是创立了一个类型为RequestMappingHandlerMapping的Bean定义,而后把它放到Spring的Bean定义注册核心。

2、完了须要做什么筹备工作?

上述第1步实现之后,容器中就有了一个类型为RequestMappingHandlerMapping的Bean定义,那么下一步就必定是要去通过这个Bean定义造一个Bean进去了呀。怎么造呢?先看看RequestMappingHandlerMapping的类的继承关系图,如下:

这个看似很简单的继承关系图,其实很简略,就是实现了一堆Spring提供的扩大点接口而已。能够把Aware结尾的扩大点看一下,外面没有什么特地的操作。再看看在RequestMappingHandlerMapping中有个InitializaingBean扩大点办法的实现,点开之后,代码如下:

@Overridepublic void afterPropertiesSet() { this.config = new RequestMappingInfo.BuilderConfiguration(); this.config.setUrlPathHelper(getUrlPathHelper()); this.config.setPathMatcher(getPathMatcher()); this.config.setSuffixPatternMatch(this.useSuffixPatternMatch); this.config.setTrailingSlashMatch(this.useTrailingSlashMatch); this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch); this.config.setContentNegotiationManager(getContentNegotiationManager());    super.afterPropertiesSet();}

仿佛和@RequestMapping并没有啥关系,然而调用了父类的afterPropertiesSet办法,关上父类中的实现之后,会发现些许猫腻,代码如下:

@Overridepublic void afterPropertiesSet() { initHandlerMethods();}

依照Spring框架中标准的命名习惯,能够猜到,这就是初始化处理器办法的,持续查看这个办法,能够看到如下的代码,依照Bean的名称做了一个过滤。咱们之前文章中介绍过,Spring中申明的Bean,你能够指定其bean的名称,也能够不指定,不指定,默认是依照类的全限定名去生成的,必定不以"scopedTarget."结尾,所以会调用processCandicateBean办法!

protected void initHandlerMethods() { for (String beanName : getCandidateBeanNames()) {  // 如果bean的名称不是以"scopedTarget."结尾  if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {   // 解决候选的Bean   processCandidateBean(beanName);  } } handlerMethodsInitialized(getHandlerMethods());}

在processCandidateBean办法中,首先依据bean名称调用BeanFactory提供的getType办法获取Bean定义的class类型,而后有个很要害的判断,代码如下:

protected void processCandidateBean(String beanName) { Class<?> beanType = null; try {  beanType = obtainApplicationContext().getType(beanName); } catch (Throwable ex) {}  // 如果依据bean名称解析进去的beanType上标注有@Controler或者@RequestMapping注解,则进行申请映射办法的解析操作. if (beanType != null && isHandler(beanType)) {  // 解析申请URL与Controller的映射关系  detectHandlerMethods(beanName); }}

这个if判断中的isHandler办法实现如下:

@Overrideprotected boolean isHandler(Class<?> beanType) { return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||   AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));}

能够看到,就是判断这个类上是否标注有@Controller注解和@RequestMapping注解。所以,能够锁定,processCandidateBean这个办法就是用来实现@RequestMapping注解性能的筹备工作的。如果class上标了这两个注解的任何一个,都会去执行detectHandlerMethods办法,即:探测处理器办法。不得不拜服写Spring框架的大神们,命名都这么活泼贴切!

完了之后,持续查看detectHandlerMethods办法,如下:

protected void detectHandlerMethods(Object handler) { // 获取Handler的class类型,即Controller对应的class Class<?> handlerType = (handler instanceof String ?   obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) {  // 以后的Class是否为代理类,判断形式是类的名称中是否含有$$符号  Class<?> userType = ClassUtils.getUserClass(handlerType);  Map<Method, T> methods = MethodIntrospector.selectMethods(userType,    (MethodIntrospector.MetadataLookup<T>) method -> {     try {      return getMappingForMethod(method, userType);     }     catch (Throwable ex) {}    });  // mapping为一个RequestMappingInfo对象  methods.forEach((method, mapping) -> {   Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);   registerHandlerMethod(handler, invocableMethod, mapping);  }); }}

这个办法看着写的比拟骚气,然而只须要确认三点即可:

(1)返回的methods为一个Map类型,key为办法对象,value是什么什么玩意儿?

查看MethodIntrospector的selectMethods办法实现,其实这个value就是这个办法第二个回调函数的返回后果,即:getMappingForMethod的后果。查看这个getMappingForMethod办法,它的实现在RequestMappingHandlerMapping中,代码如下:

@Override@Nullableprotected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { // 将办法上的@RequestMapping注解解析为RequestMappingInfo对象 RequestMappingInfo info = createRequestMappingInfo(method); if (info != null) {  // 将类上的@RequestMapping注解解析为RequestMappingInfo对象  RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);  // 如果类上有注解,则会将类上的@RequestMapping和办法上的@RequestMapping中的信息合并,例如:将requestUri合并  if (typeInfo != null) {   info = typeInfo.combine(info);  }  String prefix = getPathPrefix(handlerType);  if (prefix != null) {   info = RequestMappingInfo.paths(prefix).build().combine(info);  } } return info;}

能够看到这个办法的返回值类型是RequestMappingInfo类型,依据名称和这个办法的实现逻辑能够看到,这个对象就是用来封装办法和处理器的映射关系的。

(2)getMappingForMethod是如何解析HandlerMethod的?

它将类上的@RequestMapping注解信息和办法上的@RequestMapping信息做一个合并,通常就是咱们的申请uri合并。即:"类上有个@RequestMapping("/api/user"),listUser办法上有个@RequestMapping("/listUser")",通过typeInfo.combine之后,就变成了/api/user/listUser。再次赞一下Spring中的办法命名,太贴切了。

在上述代码中createRequestMapping的时候,底层应用了建造者设计模式,代码看着特地优雅,如下:

protected RequestMappingInfo createRequestMappingInfo(  RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) { // 创立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();}

所以就算代码没看明确,学习一下大佬们的代码习惯也挺好!

(3)失去methods之后,循环中的registerHandlerMethod是把这个办法注册到了哪?

关上registerHandlerMethod办法之后,发现注册处理器办法是通过一个映射器注册核心MappingRegistry来实现,代码如下:

protected void registerHandlerMethod(Object handler, Method method, T mapping) { this.mappingRegistry.register(mapping, handler, method);}

查看这个MappingRegistry的register办法,就看到保留@RequestMapping申请门路和处理器办法的实在逻辑了,如下:

public void register(T mapping, Object handler, Method method) { // 因为是在Spring启动的时候注册的,每个虚拟机上可能存在着反复注册的可能性,保障线程平安 this.readWriteLock.writeLock().lock(); try {  // 创立HandlerMethod对象  HandlerMethod handlerMethod = createHandlerMethod(handler, method);  // 校验HandlerMethod的唯一性  assertUniqueMethodMapping(handlerMethod, mapping);  // 注册RequestHandlerMappingInfo和HandlerMethod的对应关系  this.mappingLookup.put(mapping, handlerMethod);  // 查找URL中带有匹配符的url  List<String> directUrls = getDirectUrls(mapping);  for (String url : directUrls) {   this.urlLookup.add(url, mapping);  }  String name = null;  // 如果设置了Mapping名称的生成策略,则应用策略生成Mapping的名称  // 在创立RequestMappingHandlerMapping的时候设置的名称生成策略为:RequestMappingInfoHandlerMethodMappingNamingStrategy  if (getNamingStrategy() != null) {   name = getNamingStrategy().getName(handlerMethod, mapping);   addMappingName(name, handlerMethod);  }  // 初始化跨域配置  CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);  if (corsConfig != null) {   this.corsLookup.put(handlerMethod, corsConfig);  }  this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name)); } finally {  this.readWriteLock.writeLock().unlock(); }}

在这个办法中也有两个重要的点:
① 首先有个assertUniqueMethodMapping办法,这个办法就是用来校验RequestMapping是否惟一,有时候多个Controller中的@RequestMapping中的path写反复了,启动报错就是被这给校验住了;

② SpringMVC申请门路和处理器的映射关系,并不是间接一个url和method的映射关系,而是保留了好几套的映射关系,包含:a.mapping和HandlerMethod的;b.url和mapping的;c.mapping和MappingRegistration的,这个MappingRegistration中其实是在HandlerMethod的根底上多了一部分信息而已;d.HandlerMethod和corsConfig的,corsConfig是跨域时会应用到;

总结:在Spring进行Bean的创立过程中,通过执行InitializingBean的扩大点办法afterPropertiesSet来实现RequestMapping和HandlerMethod对应关系的解析,并注册到映射器注册核心外面,实现筹备工作!

3、筹备好了之后,如果执行?

篇幅太长,欲知后事如何,请期待下次合成。

Spring源码类文章列表:https://segmentfault.com/a/11...