Spring MVC 初始化源码(4)—@RequestMapping注解解析源码详解
2021/6/20 11:50:25
本文主要是介绍Spring MVC 初始化源码(4)—@RequestMapping注解解析源码详解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
基于最新Spring 5.x,详细介绍了Spring MVC中的@RequestMapping注解解析的源码。
我正在参与CSDN《新程序员》有奖征文,活动地址:https://marketing.csdn.net/p/52c37904f6e1b69dc392234fff425442。
采用@RequestMapping注解以及使用@RequestMapping作为元注解的注解
修饰方法来实现的Controller控制器将被解析为HandlerMethod
类型的Handler处理器对象,该对象保存着URI路径到对应控制器方法
的映射关系,后面请求的时候会根据路径
找到Handler,然后拿到Handler对应的控制器方法直接执行,而无需再次解析。
下面让我一起来看看基于@RequestMapping注解的Controller控制器的解析流程源码。下面的源码版本基于5.2.8.RELEASE
。
Spring MVC源码 系列文章
Spring MVC 初始化源码(1)—ContextLoaderListener与父上下文容器的初始化
Spring MVC 初始化源码(2)—DispatcherServlet与子容器的初始化以及MVC组件的初始化【一万字】
Spring MVC 初始化源码(3)—<mvc:annotation-driven >配置标签的源码解析
Spring MVC 初始化源码(4)—@RequestMapping注解的源码解析
文章目录
- Spring MVC源码 系列文章
- 1 @RequestMapping注解解析入口
- 2 getCandidateBeanNames获取候选beanName
- 3 processCandidateBean处理候选bean
- 3.1 isHandler是否是handler类
- 3.2 detectHandlerMethods解析HandlerMethod
- 3.2.1 getMappingForMethod获取RequestMappingInfo映射
- 3.2.1.1 createRequestMappingInfo创建RequestMappingInfo
- 3.2.1.2 combine合并属性
- 3.2.1.3 pathPrefixes路径前缀
- 3.2.2 registerHandlerMethod注册HandlerMethod
- 3.2.2.1 MappingRegistry映射注册表
- 4 总结
1 @RequestMapping注解解析入口
通常Handler处理器
在容器启动的时候就被解析了,因此HandlerMethod
也在启动时解析的,RequestMappingHandlerMapping
实现了InitializingBean
接口,在RequestMappingHandlerMapping被实例化之后的将会触发它的InitializingBean#afterPropertiesSet
方法回调,在该方法中就会进行基于注解的控制器方法的解析。
下面来看看基于@RequestMapping注解的方法控制器的解析原理。
/** * RequestMappingHandlerMapping的属性 * <p> * 用于请求映射目的的配置选项的容器。 * 这种配置是创建RequestMappingInfo实例所必需的,通常在所有RequestMappingInfo实例创建中使用。 */ private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration(); /** * RequestMappingHandlerMapping的方法 */ @Override @SuppressWarnings("deprecation") public void afterPropertiesSet() { //将一系列属性设置到RequestMappingInfo.BuilderConfiguration中 this.config = new RequestMappingInfo.BuilderConfiguration(); this.config.setUrlPathHelper(getUrlPathHelper()); this.config.setPathMatcher(getPathMatcher()); this.config.setSuffixPatternMatch(useSuffixPatternMatch()); this.config.setTrailingSlashMatch(useTrailingSlashMatch()); this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch()); this.config.setContentNegotiationManager(getContentNegotiationManager()); /*调用父类AbstractHandlerMethodMapping的方法*/ super.afterPropertiesSet(); } /** * AbstractHandlerMethodMapping的方法 * <p> * 在初始化时立即检测处理器方法。 */ @Override public void afterPropertiesSet() { //调用initHandlerMethods方法 initHandlerMethods(); }
initHandlerMethods
方法用于在ApplicationContext中扫描bean,检测并注册Handler方法。
/** * 使用了scoped-proxy作用域代理的Bean名称前缀,前缀之后就是原始beanName */ private static final String SCOPED_TARGET_NAME_PREFIX = "scopedTarget."; /** * AbstractHandlerMethodMapping的方法 * <p> * 在ApplicationContext中扫描bean,检测并注册Handler方法。 */ protected void initHandlerMethods() { //getCandidateBeanNames()实际上会获取当前Spring MVC容器中所有bean的beanName,默认不会获取父Spring容器中的beanName //因此建议对不同类型的bean定义在不同的容器中,这样这里的循环遍历判断的时间就会有效减少 for (String beanName : getCandidateBeanNames()) { //如果beanName不是以"scopedTarget."开头的,即不是作用域代理bean,那么处理此bean if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { //处理候选bean.确定指定的候选bean的类型,如果是handler类型 //那么调用detectHandlerMethods方法在指定的 handler bean中查找handler方法 //并注册一系列缓存到当前AbstractHandlerMethodMapping实例的mappingRegistry属性中(该属性对象内部的数据集合中)。 processCandidateBean(beanName); } } //在检测到所有handler method之后调用,方法参数是mappingLookup缓存 //目前版本中该方法主要目的仅仅是尝试打印日志信息,比如handlerMethods的总数 handlerMethodsInitialized(getHandlerMethods()); }
可以看到,initHandlerMethods
方法将会获取当前Spring MVC容器中所有bean的beanName作为候选bean,默认不会
获取父Spring容器中的beanName。
随后对每一个候选bean循环调用processCandidateBean
方法,该方法中将确定指定的候选bean的类型,如果是handler bean
,那么调用detectHandlerMethods
方法在指定的 handler bean中查找handler方法,并注册一系列缓存到当前AbstractHandlerMethodMapping
实例的mappingRegistry
属性中(该属性对象内部的缓存集合中)。
2 getCandidateBeanNames获取候选beanName
getCandidateBeanNames()
实际上会获取当前Spring MVC容器中所有bean的beanName,默认不会
获取父Spring容器中的beanName。因此建议对不同类型的bean定义在不同的容器中,这样这里的循环遍历判断的时间
就会有效减少
。
这个标志
很重要,默认情况下,父容器中的Handler方法不会被检测,仅会检测DispatcherServlet关联的子容器
中的Handler方法。因此,一般情况下,如果Controller被存放在父容器中,则该Controller是失效的。
这里的原理源码。
/** * AbstractHandlerMethodMapping的属性 * <p> * 是否在祖先ApplicationContexts中的Bean中检测处理器方法。 * 默认值为“ false”:仅考虑当前ApplicationContext中的bean,即仅在定义了此HandlerMapping本身的上下文中(通常是当前DispatcherServlet的上下文)。 * 将此标志也打开,以检测祖先上下文(通常是Spring Root WebApplicationContext)中的处理程序bean。 * <p> * 这个标志很重要,默认情况下,父容器中的Handler方法不会被检测,仅会检测DispatcherServlet关联的子容器中的Handler方法 * 因此,一般情况下,如果Controller被存放在父容器中,则该Controller是失效的。 */ private boolean detectHandlerMethodsInAncestorContexts = false; /** * AbstractHandlerMethodMapping的方法 * <p> * 在应用程序上下文中确定候选bean的名称。 * * @return 候选bean的名称数组,默认是容器中的全部beanName */ protected String[] getCandidateBeanNames() { //是否在祖先ApplicationContexts中的Bean中检测处理器方法,默认false,将仅在当前DispatcherServlet的上下文检测 //这里是尝试获取Object类型的beanName数组,由于类型是Object因此将会获取所有的注册的beanName return (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : obtainApplicationContext().getBeanNamesForType(Object.class)); }
3 processCandidateBean处理候选bean
确定指定的候选bean的类型,如果是handler
类型,那么调用detectHandlerMethods
方法在在指定的 handler bean中查找handler方法,并注册一系列缓存到当前AbstractHandlerMethodMapping
实例的mappingRegistry
属性中(该属性对象内部的数据集合中)。
/** * AbstractHandlerMethodMapping的方法 * <p> * 确定指定的候选bean的类型,如果是handler类型,那么调用detectHandlerMethods方法 * <p> * 此实现通过检查org.springframework.beans.factory.BeanFactory#getType * 并使用bean名称调用detectHandlerMethods方法来避免bean创建。 * * @param beanName 候选bean的名称 */ protected void processCandidateBean(String beanName) { Class<?> beanType = null; try { //根据beanName获取bean类型 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); } } //如果isHandler方法返回true,即当前类属于handler类 if (beanType != null && isHandler(beanType)) { //那么在指定的handler bean中查找handler方法 //并注册到当前AbstractHandlerMethodMapping实例的mappingRegistry属性中 detectHandlerMethods(beanName); } }
3.1 isHandler是否是handler类
该方法判断给定类型是否是一个handler
类,期望处理器类具有类级别的@Controller注解或类型级别的@RequestMapping注解
,也就是说bean的类上如果具有这两个注解之一
,那么该类就是handler类型。
/** * RequestMappingHandlerMapping的方法 * <p> * 给定类型是否是一个handler类 * <p> * 期望处理器类具有类级别的@Controller注解或类型级别的@RequestMapping注解 * 如果具有这两个注解之一,那么该类就是handler类型 * * @param beanType 被检查的bean的类型 * @return 如果是handler类型,则为“ true”,否则为“ false”。 */ @Override protected boolean isHandler(Class<?> beanType) { //类上是否具有@Controller或者@RequestMapping注解 return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)); }
3.2 detectHandlerMethods解析HandlerMethod
在指定的handler bean中查找handler方法,并注册到当前AbstractHandlerMethodMapping
实例的mappingRegistry
属性中。
/** * AbstractHandlerMethodMapping的方法 * <p> * 在指定的handler bean中查找handler方法并注册到缓存中 * * @param handler Bean名称或实际handler实例 */ protected void detectHandlerMethods(Object handler) { //获取handler的类型 Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { Class<?> userType = ClassUtils.getUserClass(handlerType); //查找该类的方法,并解析方法和类上的@RequestMapping注解创建RequestMappingInfo对象,T为RequestMappingInfo类型的实例 //最终返回一个map,每个方法都有对应的RequestMappingInfo实例,如果该方法上没有@RequestMapping注解,则value为null Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> { try { /* * 1 查找该方法对应的RequestMappingInfo映射 * * 该方法由子类实现,对于RequestMappingHandlerMapping,实际上就是 * 解析方法上的@RequestMapping注解并创建一个RequestMappingInfo对象 * 即将@RequestMapping注解的属性设置给RequestMappingInfo对应的属性,如果没有该注解,则返回null */ 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)); } //遍历获取到的map methods.forEach((method, mapping) -> { //根据对象的类型(可能是代理对象类型),解析当前方法成为一个真正可执行方法 //为什么要这么做?因为实际执行该方法的对象可能是一个代理对象,进而在调用该方法时抛出各种异常 //这个方法我们在Spring事件发布机制的源码文章中就详细讲解过了,在此不再赘述 Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); /* * 2 注册handlerMethod的一系列映射关系 */ registerHandlerMethod(handler, invocableMethod, mapping); }); } }
3.2.1 getMappingForMethod获取RequestMappingInfo映射
解析方法和类级别的@RequestMapping注解(或者以该注解为元注解的注解)
,创建该handler方法的RequestMappingInfo
映射。如果方法没有@RequestMapping注解,则获取null。
该方法由子类实现,对于RequestMappingHandlerMapping
,实际上就是解析方法上的@RequestMapping
注解并创建一个RequestMappingInfo
对象,即将@RequestMapping注解的属性设置给RequestMappingInfo对应的属性,如果没有该注解,则返回null。
如果在类和方法上都存在@RequestMapping注解。那么最终的RequestMappingInfo将会合
并两个注解的属性,比如对于该handler的访问URI
,一般就是类上的注解的path路径位于方法上的注解的path路径之前。
/** * RequestMappingHandlerMapping的方法 * <p> * 解析方法和类级别的@RequestMapping注解(或者以该注解为元注解的注解)创建该handler方法的RequestMappingInfo映射。 * * @param handlerType handler类的CLass * @param method handler类的某个Method * @return 创建的RequestMappingInfo;如果方法没有@RequestMapping注解,则为null。 */ @Override @Nullable protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { //解析方法上的@RequestMapping注解并创建一个RequestMappingInfo对象 //即将@RequestMapping注解的属性设置给RequestMappingInfo对应的属性,如果没有该注解,则返回null RequestMappingInfo info = createRequestMappingInfo(method); if (info != null) { //解析类上的@RequestMapping注解并创建一个RequestMappingInfo对象 //即将@RequestMapping注解的属性设置给RequestMappingInfo对应的属性,如果没有该注解,则返回null RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); //如果类上存在@RequestMapping注解注解 if (typeInfo != null) { //那么类上的注解信息和方法上的注解信息联合起来,也就是合并一些属性 //对于path路径属性的联合,主要就是在方法的注解path前面加上类上的注解的path路径 info = typeInfo.combine(info); } //获取该handler的额外访问URI路径前缀,一般为null //但我们可以手动配置RequestMappingHandlerMapping的pathPrefixes属性 String prefix = getPathPrefix(handlerType); //如果存在对该handler的URI前缀 if (prefix != null) { //则在path前面继续加上路径前缀,后续访问时可以加上前缀路径,也可以不加 info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info); } } return info; }
3.2.1.1 createRequestMappingInfo创建RequestMappingInfo
该方法查找方法/类上的@RequestMapping
注解并创建一个RequestMappingInfo
,构建者
模式。该注解可以是直接声明的注解,元注解,也可以是在注解层次结构中合并注解属性的综合结果(即这个注解可以来自于父类)。
/** * RequestMappingHandlerMapping的方法 * <p> * 委托createRequestMappingInfo(RequestMapping,RequestCondition) * 根据所提供的annotatedElement是类还是方法来提供适当的自定义RequestCondition。 * * @param element 需要创建RequestMappingInfo的源数据,可能是方法或者类 */ @Nullable private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { //获取方法/类上的@RequestMapping注解,支持从父类的方法/类上查找 RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); //处理该请求的条件,这个版本无论是方法还是类都返回null RequestCondition<?> condition = (element instanceof Class ? getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element)); //通过方法上的@RequestMapping注解以及RequestCondition创建一个RequestMappingInfo, return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null); } /** * RequestMappingHandlerMapping的方法 * <p> * 通过方法/类上的@RequestMapping注解创建一个RequestMappingInfo,构建者模式。 * 该注解可以是直接声明的注解,元注解,也可以是在注解层次结构中合并注解属性的综合结果。 * * @param customCondition 自定义的条件 * @param requestMapping 注解 */ protected RequestMappingInfo createRequestMappingInfo( RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) { RequestMappingInfo.Builder builder = RequestMappingInfo //解析注解的value和path属性,即请求的URI路径,这是一个数组 //path路径支持${..:..}占位符,并且支持普通方式从外部配置文件中加载进来的属性以及environment的属性。 //path路径还支持SPEL表达式(首先解析占位符,然后解析SPEL表达式) .paths(resolveEmbeddedValuesInPatterns(requestMapping.path())) //解析注解的method属性,即请求方法,这是一个数组 .methods(requestMapping.method()) //解析注解的params属性,即请求参数匹配,这是一个数组 .params(requestMapping.params()) //解析注解的headers属性,即请求头匹配,这是一个数组 .headers(requestMapping.headers()) //解析注解的consumes属性,即请求的Content-Type匹配,这是一个数组 .consumes(requestMapping.consumes()) //解析注解的produces属性,即请求的Accept匹配,这是一个数组 .produces(requestMapping.produces()) //解析注解的name属性,即映射名称 .mappingName(requestMapping.name()); if (customCondition != null) { builder.customCondition(customCondition); } //构建RequestMappingInfo return builder.options(this.config).build(); }
3.2.1.2 combine合并属性
如果方法和类上都有@RequestMapping注解,那么对于俩个注解的属性执行属性合并操作,最终返回一个新的RequestMappingInfo对象。
/** * RequestMappingInfo的方法 * <p> * 将“此”请求映射信息(即当前实例)与另一个请求映射信息实例结合起来。 * * @param other 另一个请求映射信息实例.(即方法上的注解解析的RequestMappingInfo) * @return 一个新的请求映射信息实例,永不为null */ @Override public RequestMappingInfo combine(RequestMappingInfo other) { //name属性,只有一个注解具有该属性,那么就是使用该注解设置的属性 //如果两个注解都具有该属性,那么使用"#"联合这两个属性,类注解属性在前 String name = combineNames(other); //合并PatternsRequestCondition,最主要就是合并两个注解的path属性,即URI路径 //原理比较复杂,将会对两个注解的path数组对应的索引的值进行合并 //如果只有一个注解具有该属性,那么就是使用该注解设置的属性 //如果两个注解都具有该属性,那么要分情况讨论,比较复杂,比如路径通配符,后缀通配符等 //最常见的情况就是在方法的注解path前面加上类上的注解的path路径,它们之间使用"/"分隔 PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition); //合并methods、Params、headers属性,如果只有一个注解具有该属性,那么就是使用该注解设置的属性 //否则最终合并的结果将是两个注解的属性直接合并之后的结果(去重) RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition); ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition); HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition); //合并consumes、produces属性,如果参数注解(方法上的注解)具有该属性,那么就使用参数注解的属性,否则使用当前注解的属性(类注解的属性) ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition); ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition); //合并customCondition属性,一般都为null RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder); //根据合并的属性创建一个新的RequestMappingInfo return new RequestMappingInfo(name, patterns, methods, params, headers, consumes, produces, custom.getCondition()); }
3.2.1.3 pathPrefixes路径前缀
自Spring5.1开始,支持为某个handler类下面的所有handler方法配置URI路径前缀,在请求的时候就可以在path之前加上访加上前缀路径,当前也可以不加。
前缀路径通过RequestMappingHandlerMapping的pathPrefixes属性
来配置,该属性是一个LinkedHashMap。key为前缀路径字符串,value为对应的Predicate断言,如果某个handler的Class满足Predicate,那么就可以使用key作为路经前缀。
在断言的时候,如果因此如果有多个能够匹配的Predicate
,由于是一次匹配,并且集合是LinkedHashMap,那么配置在前面
的路径前缀将被使用。
//RequestMappingHandlerMapping的属性 /** * SPring 5.1新增的路径前缀属性map * <p> * key为前缀路径字符串,value为对应的Predicate断言,如果某个handler的Class满足Predicate,那么就可以使用key作为路经前缀 */ private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>(); /** * 用于执行${..:..}占位符匹配和SPEL表达式解析 */ @Nullable private StringValueResolver embeddedValueResolver; /** * RequestMappingHandlerMapping的方法 * <p> * 获取当前handler类的路径前缀 * * @param handlerType 当前handler类的Class * @return 前缀路径,没有则返回null */ @Nullable String getPathPrefix(Class<?> handlerType) { //遍历map依次匹配,因此如果有多个能够匹配的Predicate,那么配置在前面的最先匹配 for (Map.Entry<String, Predicate<Class<?>>> entry : this.pathPrefixes.entrySet()) { //执行Predicate的test方法尝试断言,如果可以匹配该Class,那么获取key作为路径前缀 if (entry.getValue().test(handlerType)) { String prefix = entry.getKey(); //前缀路径字符串还支持${..:..}占位符匹配和SPEL表达式 if (this.embeddedValueResolver != null) { prefix = this.embeddedValueResolver.resolveStringValue(prefix); } return prefix; } } return null; }
3.2.2 registerHandlerMethod注册HandlerMethod
注册handler方法及其唯一映射的一系列对应关系,在项目启动时会为每个检测到的handler方法调用该方法,后续请求到来的时候可以直接
从注册表中获取并使用,无需再次解析。
/** * RequestMappingHandlerMapping的方法 * * @param handler handler的beanName或者handler实例 * @param method 注册方法 * @param mapping 与handler方法关联的映射条件RequestMappingInfo */ @Override protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) { //调用父类AbstractHandlerMethodMapping的同名方法 super.registerHandlerMethod(handler, method, mapping); //更新ConsumesCondition的bodyRequired属性,该属性从方法参数上的@RequestBody注解的required属性中获取。 updateConsumesCondition(mapping, method); }
其内部首先调用父类AbstractHandlerMethodMapping
的同名方法,该方法真正的实现HandlerMethod的注册。
/** * AbstractHandlerMethodMapping的属性 * <p> * handler方法映射注册表 */ private final MappingRegistry mappingRegistry = new MappingRegistry(); /** * AbstractHandlerMethodMapping的方法 * <p> * 注册handler方法及其唯一映射的对应关系,在项目启动时会为每个检测到的handler方法调用该方法。 * * @param handler handler的beanName或者handler实例 * @param method 注册方法 * @param mapping 与handler方法关联的映射条件RequestMappingInfo * @throws IllegalStateException 如果已经在同一映射下注册了另一种方法 */ protected void registerHandlerMethod(Object handler, Method method, T mapping) { //调用mappingRegistry的register方法 this.mappingRegistry.register(mapping, handler, method); }
3.2.2.1 MappingRegistry映射注册表
MappingRegistry
是AbstractHandlerMethodMapping
的内部类,这是一个注册表的实现,用于维护到handler method的所有映射缓存。
核心方法就是register
方法。
/** * AbstractHandlerMethodMapping的内部类 * <p> * 一个注册表,用于维护到handler method的所有映射缓存。 */ class MappingRegistry { /** * RequestMappingInfo到MappingRegistration的缓存 */ private final Map<T, MappingRegistration<T>> registry = new HashMap<>(); /** * RequestMappingInfo到HandlerMethod的缓存 */ private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>(); /** * url路径到RequestMappingInfo的缓存,url可以存在通配符等特殊字符 * 这里的value可以是多个RequestMappingInfo,因为一个url路径可能对应多个RequestMappingInfo * 它们使用请求方法来区分,因此使用MultiValueMap。 */ private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>(); /** * mapping name到多个HandlerMethod的缓存 */ private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>(); /** * HandlerMethod到CorsConfiguration的缓存,用于Cors跨域的处理 */ private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>(); /** * 一个读写锁,提升效率 */ private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); /** * 返回所有mapping和handler方法。 */ public Map<T, HandlerMethod> getMappings() { return this.mappingLookup; } /** * 返回给定URL路径的匹配mapping */ @Nullable public List<T> getMappingsByUrl(String urlPath) { return this.urlLookup.get(urlPath); } /** * 通过mapping name返回handler method。 */ public List<HandlerMethod> getHandlerMethodsByMappingName(String mappingName) { return this.nameLookup.get(mappingName); } /** * 返回给定handlerMethod的CORS配置 */ @Nullable public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) { HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod(); return this.corsLookup.get(original != null ? original : handlerMethod); } /** * 调用getMappings和getMappingsByUrl方法之前获取读锁 */ public void acquireReadLock() { this.readWriteLock.readLock().lock(); } /** * 调用getMappings和getMappingsByUrl方法之后释放读锁 */ public void releaseReadLock() { this.readWriteLock.readLock().unlock(); } /** * 注册Mapping、handler、Method之间的一系列映射缓存,将会使用写锁保证线程安全 * * @param mapping 映射,对于RequestMappingHandlerMapping来说就是RequestMappingInfo * @param handler handler类的beanName或者handler实例 * @param method handler的某个方法 */ public void register(T mapping, Object handler, Method method) { //Kotlin语言支持 if (KotlinDetector.isKotlinType(method.getDeclaringClass())) { Class<?>[] parameterTypes = method.getParameterTypes(); if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) { throw new IllegalStateException("Unsupported suspending handler method detected: " + method); } } //阻塞的获取写锁 this.readWriteLock.writeLock().lock(); try { //根据handler和method创建HandlerMethod,实际上就是new一个新的HandlerMethod对象,该对象内部保存了 //bean(beanName/bean实例)、beanType、method(给定的方法)、parameters(方法参数)等等属性 HandlerMethod handlerMethod = createHandlerMethod(handler, method); //检验mappingLookup,因为一个mapping只能对应一个handlerMethod //如果当前mapping对应了多个HandlerMethod,则会抛出异常:Ambiguous mapping. Cannot map validateMethodMapping(handlerMethod, mapping); /*mapping和handlerMethod存入mappingLookup缓存*/ this.mappingLookup.put(mapping, handlerMethod); /*url路径和mapping的关系存入urlLookup缓存*/ List<String> directUrls = getDirectUrls(mapping); for (String url : directUrls) { this.urlLookup.add(url, mapping); } /*name和mapping的关系存入nameLookup缓存*/ String name = null; if (getNamingStrategy() != null) { name = getNamingStrategy().getName(handlerMethod, mapping); addMappingName(name, handlerMethod); } /* * 初始化当前方法的Cors跨域属性配置类 * 会尝试获取当前方法以及方法所属的类上的@CrossOrigin注解,并会对他们进行合并 */ CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); if (corsConfig != null) { /*handlerMethod和corsConfig存入corsLookup缓存*/ this.corsLookup.put(handlerMethod, corsConfig); } /* * mapping和MappingRegistration存入mappingLookup缓存 * MappingRegistration对象相当于其他属性的一个集合,从该对象可以获取当前映射对应的的各种信息 * 比如mapping、handlerMethod、directUrls、name */ this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name)); } finally { //释放写锁 this.readWriteLock.writeLock().unlock(); } } private void validateMethodMapping(HandlerMethod handlerMethod, T mapping) { // 获取该mapping对应的HandlerMethod HandlerMethod existingHandlerMethod = this.mappingLookup.get(mapping); //如果existingHandlerMethod不为null,并且缓存中的handlerMethod和新建的handlerMethod不相等,那么抛出异常 if (existingHandlerMethod != null && !existingHandlerMethod.equals(handlerMethod)) { throw new IllegalStateException( "Ambiguous mapping. Cannot map '" + handlerMethod.getBean() + "' method \n" + handlerMethod + "\nto " + mapping + ": There is already '" + existingHandlerMethod.getBean() + "' bean method\n" + existingHandlerMethod + " mapped."); } } //……省略其他方法 }
4 总结
基于@RequestMapping注解的Handler方法在Spring MVC容器启动的时候就会被解析并缓存起来,将会被解析为HandlerMethod对象,后续请求进来的时候通过HandlerMethod可以找到某个请求到具体的handler方法的映射关系,无需再次解析。
基于@RequestMapping注解的Handler方法在解析时,默认情况下,父容器中的Handler方法不会被检测,仅会检测DispatcherServlet关联的子容器
中的Handler方法。因此,一般情况下,如果Controller被存放在父容器中,则该Controller是失效的。
但是可以通过设置detectHandlerMethodsInAncestorContexts
属性为true来表示启动父容器中的基于@RequestMapping注解的Handler方法的解析。
相关文章:
https://spring.io/
Spring Framework 5.x 学习
Spring MVC 5.x 学习
Spring Framework 5.x 源码
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!
这篇关于Spring MVC 初始化源码(4)—@RequestMapping注解解析源码详解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南