SpringMVC--@RequestMapping注解标注方法解析

本文是基于springboot进行源码追踪分析

问题

  • @RequestMapping注释的类及方法,Spring是何时,何种方式解析成url与方法的映射关系的?

背景

  • @RequestMapping注解的解析识别工作是由RequestMappingHandlerMapping类去完成的,会生成对应的RequestMappingInfo实例
  • RequestMappingHandlerMapping类的位置是在org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration通过@Bean注解声明的
  • org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors方法解析通过@Bean标记的方法,将对应对象转换为org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.ConfigurationClassBeanDefinition注册到org.springframework.beans.factory.support.DefaultListableBeanFactory
  • org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons方法中实例化对象,并在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeInitMethods方法中调用org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#afterPropertiesSet方法实现@RequestMapping注解类及方法的解析与注册

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration中定义了WEB MVC相关的自动配置类,就比如org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMappingorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter等等的实例化,

解析

类继承图

过程

在初始化RequestMappingHandlerMapping对象的时候,因为实现了org.springframework.beans.factory.InitializingBean#afterPropertiesSet方法,所以会调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeInitMethods方法时,会调用RequestMappingHandlerMapping#afterPropertiesSet方法。

	// RequestMappingHandlerMapping类中相关代码

	public void afterPropertiesSet() {

		this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setTrailingSlashMatch(useTrailingSlashMatch());
this.config.setContentNegotiationManager(getContentNegotiationManager()); if (getPatternParser() != null) {
this.config.setPatternParser(getPatternParser());
Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch,
"Suffix pattern matching not supported with PathPatternParser.");
}
else {
this.config.setSuffixPatternMatch(useSuffixPatternMatch());
this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
this.config.setPathMatcher(getPathMatcher());
}
// 调用父类的方法进行具体的解析
super.afterPropertiesSet();
}

在自身类的重写方法中进行了一系列的配置,同时调用了父类(org.springframework.web.servlet.handler.AbstractHandlerMethodMapping)的afterPropertiesSet方法,而具体的解析方法就在父类中。

	// AbstractHandlerMethodMapping中相关代码	

	public void afterPropertiesSet() {
initHandlerMethods();
}
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #getCandidateBeanNames()
* @see #processCandidateBean
* @see #handlerMethodsInitialized
*/
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
//处理每个可能的bean
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
} protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
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上是否由@Controller或者@RequestMapping,如果有则说明是一个待解析的RequestMappingInfo
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
} protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
//获取该handler内所有的Method与RquestMappingInfo映射关系
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));
}
else if (mappingsLogger.isDebugEnabled()) {
mappingsLogger.debug(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//按method将RequestMappingInfo进行注册
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}

AbstractHandlerMethodMapping#detectHandlerMethods方法中,获取当前bean的所有method与RequestMapping映射关系,并进行注册。

现在继续看AbstractHandlerMethodMapping#getMappingForMethod,根据方法名即可猜测,这里就是通过method获取RequestMappingInfo,具体的实现方法在RequestMappingHandlerMapping#createRequestMappingInfo

// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod实现代码

	/**
* Uses method and type-level @{@link RequestMapping} annotations to create
* the RequestMappingInfo.
* @return the created RequestMappingInfo, or {@code null} if the method
* does not have a {@code @RequestMapping} annotation.
* @see #getCustomMethodCondition(Method)
* @see #getCustomTypeCondition(Class)
*/
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
// 根据method创建对应的RquestMappingInfo
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
// 如果方法所在类上也标注了@RequestMapping,则创建类对应的RequestMappingInfo
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
// 将类与方法的requestMappingInfo进行合并,可以理解为获取完整的url路径
info = typeInfo.combine(info);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
// 返回完整url的RequestMappingInfo对象
return info;
}

获取到对应的RequestMappingInfo之后,就需要进行注册了,下面看注册逻辑org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerHandlerMethod

	public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
// 根据方法生成对应的HandlerMethod,包含了method对应的bean实例,对应的method
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
// 校验url路径是否有重复,如果重复会抛出异常
validateMethodMapping(handlerMethod, mapping); Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
for (String path : directPaths) {
this.pathLookup.add(path, mapping);
} String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
} CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
corsConfig.validateAllowCredentials();
this.corsLookup.put(handlerMethod, corsConfig);
} //MappingRegistration包含RequestMappingInfo、HandlerMethod等
//Map<T, MappingRegistration<T>> registry = new HashMap<>();
this.registry.put(mapping,
new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}

总结

  • 契机:给需要暴漏接口的方法、类上添加@RequestMapping注解

  • 时机:由于org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping间接实现了org.springframework.beans.factory.InitializingBean#afterPropertiesSet方法,所以在RequestMappingHandlerMapping对象初始化的时候,会调用自身的org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#afterPropertiesSet方法,自身进行一系列配置之后,就会调用父类org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#afterPropertiesSet的方法进行@RequestMapping标注方法的解析

  • 解析:主体流程都是在AbstractHandlerMethodMapping类中的方法,具体的实现通过抽象方法的形式让子类RequestMappingHandlerMapping进行实现。具体根据method生成对应的RequestMappingInfo是在RequestMappingHandlerMapping类中的方法org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod。过程中会先解析标注了@RequestMapping的方法,生成方法对应的RequestMappingInfo实例;如果方法所在的类上也有@RequestMapping标注的注解,生成类对应的RequestMappingInfo实例,然后将两者进行合并,也就是生成完整的url映射对象

  • 注册:在解析完所有的方法之后,将RequestMappingInfo进行注册,注册容器位于AbstractHandlerMethodMapping类中,容器为 MappingRegistry mappingRegistry = new MappingRegistry(),而MappingRegistry 中实际存储的容器为Map<T, MappingRegistration<T>> registry = new HashMap<>()。其中key为对应的RequestMappingInfo,value为MappingRegistration,其中MappingRegistration包含了RequestMappingInfoHandlerMethod

SpringMVC--@RequestMapping注解标注方法解析的更多相关文章

  1. SpringMVC RequestMapping注解

    1.@RequestMapping 除了修饰方法,还可以修饰类 2.类定义处:提供初步的请求映射信息.相对于WEB应用的根目录  方法处:提供进一步细分映射信息  相对于类定义处的URL.若类定义处未 ...

  2. SpringMVC @RequestMapping注解详解

    @RequestMapping 参数说明 value:定义处理方法的请求的 URL 地址.(重点) method:定义处理方法的 http method 类型,如 GET.POST 等.(重点) pa ...

  3. 超详细 SpringMVC @RequestMapping 注解使用技巧

    @RequestMapping 是 Spring Web 应用程序中最常被用到的注解之一.这个注解会将 HTTP 请求映射到 MVC 和 REST 控制器的处理方法上. 在这篇文章中,你将会看到 @R ...

  4. springMVC的注解@PathVariable是什么?详情及用法解析

    在路由中定义变量规则后,通常我们需要在处理方法(也就是@RequestMapping注解的方法)中获取这个URL变量的具体值,并根据这个值(例如用户名)做相应的操作,Spring MVC提供的@Pat ...

  5. springMVC的注解详解

    springmvc常用注解标签详解 1.@Controller 在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业 ...

  6. springmvc常用注解标签详解

    1.@Controller 在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ...

  7. springmvc常用注解标签详解【转】

    转载自:http://www.cnblogs.com/leskang/p/5445698.html 1.@Controller 在SpringMVC 中,控制器Controller 负责处理由Disp ...

  8. 一 : springmvc常用注解

    springmvc常用注解详解1.@Controller在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层 ...

  9. 转:springmvc常用注解标签详解

    Spring5:@Autowired注解.@Resource注解和@Service注解 - IT·达人 - 博客园--这篇顺序渐进,讲得超级好--此人博客很不错http://www.cnblogs.c ...

随机推荐

  1. css处理工具PostCss

    在 Web 应用开发中,CSS 代码的编写是重要的一部分.CSS 规范从最初的 CSS1 到现在的 CSS3,再到 CSS 规范的下一步版本,规范本身一直在不断的发展演化之中.这给开发人员带来了效率上 ...

  2. [源码解析] PyTorch 分布式之 ZeroRedundancyOptimizer

    [源码解析] PyTorch 分布式之 ZeroRedundancyOptimizer 目录 [源码解析] PyTorch 分布式之 ZeroRedundancyOptimizer 0x00 摘要 0 ...

  3. Numpy实现简单BP神经网络识别手写数字

    本文将用Numpy实现简单BP神经网络完成对手写数字图片的识别,数据集为42000张带标签的28x28像素手写数字图像.在计算机完成对手写数字图片的识别过程中,代表图片的28x28=764个像素的特征 ...

  4. HashMap原理及源码分析

    HashMap 原理及源码分析 1. 存储结构 HashMap 内部是由 Node 类型的数组实现的.Node 包含着键值对,内部有四个字段,从 next 字段我们可以看出,Node 是一个链表.即数 ...

  5. 微服务架构 | 12.1 使用 Apache Dubbo 实现远程通信

    目录 前言 1. Dubbo 基础知识 1.1 Dubbo 是什么 1.2 Dubbo 的架构图 1.3 Spring Cloud 与 Dubbo 的区别 1.4 Dubbo 的特点 1.5 Dubb ...

  6. 2022GDUT寒训专题一J题

    题目 题面 给你一个长度为 n的整数序列{A1,A2,⋯,A**n},要求从中找出一段连续的长度不超过 m的非空子序列,使得这个序列的和最大. 输入格式 第一行为两个整数 n,m: 第二行为 n个用空 ...

  7. vue学习4-class和sytle绑定

    #### Class绑定: 1. 通过数组的方式来实现: 2. 通过对象的方式来实现: 通过对象: 通过数组,通过数组是把多个style样式对象添加进去:

  8. go get失败解决办法

    go get时由于防火墙的原因,会导致失败.目前可以通过修改GOPROXY的方法解决该问题. 无论是在win下还是linux,macos下,只需要将环境变量GOPROXY设置成https://gopr ...

  9. 报错org.springframework.beans.factory.CannotLoadBeanClassException: Error loading class [org.mybatis.spring.SqlSessionFactoryBean]

    超级大坑 org.springframework.beans.factory.CannotLoadBeanClassException: Error loading class [org.mybati ...

  10. python11day

    昨日回顾 函数的参数: 实参角度:位置参数.关键字参数.混合参数 形参角度:位置参数.默认参数.仅限关键字参数.万能参数 形参角度参数顺序:位置参数,*args,默认参数,仅限关键字参数,**kwar ...