基于 Spring Framework v5.2.6.RELEASE
概述
上一篇主要分析了 CommonAnnotationBeanPostProcessor 的作用、它主要实现的后处理方法,并对 JSR-250 规范做了简单介绍。Spring 主要支持了 JSR-250 规范的三个注解,即@
Resource
、@
PostConstruct
和@
PreDestroy
。这篇文章主要分析 Spring 通过 CommonAnnotationBeanPostProcessor 后处理器对@Resource
注解支持的原理。对@Resource
注解的支持,主要通过postProcessMergedBeanDefinition
和postProcessProperties
两个方法来完成,我们按照两个方法被调用的顺序,来分别分析。
postProcessMergedBeanDefinition 方法和 @Resource 注解的解析
首先进入postProcessMergedBeanDefinition
方法。
// org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinitionpublicvoidpostProcessMergedBeanDefinition(RootBeanDefinitionbeanDefinition, Class<?>beanType, StringbeanName) { super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName); InjectionMetadatametadata=findResourceMetadata(beanName, beanType, null); metadata.checkConfigMembers(beanDefinition); }
方法的第一步是调用了父类的同名方法,也就是 InitDestroyAnnotationBeanPostProcessor 类中的同名方法。不过,其父类的方法中的逻辑,与本文介绍了@
Resource
注解相关的处理逻辑没有关系,因此,先跳过这部步骤,下一篇介绍@PostConstruct
和@PreDestroy
注解的处理时,再进行分析。我们直接看下面的两行代码。
通过findResourceMetadata
获取到一个 InjectionMetadata 类型的变量metadata
,然后再调用其checkConfigMembers
方法。
我们先看findResourceMetadata
方法
获取注解元信息的 findResourceMetadata 方法
// org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#findResourceMetadataprivateInjectionMetadatafindResourceMetadata(StringbeanName, finalClass<?>clazz, PropertyValuespvs) { // Fall back to class name as cache key, for backwards compatibility with custom callers.StringcacheKey= (StringUtils.hasLength(beanName) ?beanName : clazz.getName()); // Quick check on the concurrent map first, with minimal locking.InjectionMetadatametadata=this.injectionMetadataCache.get(cacheKey); if (InjectionMetadata.needsRefresh(metadata, clazz)) { synchronized (this.injectionMetadataCache) { metadata=this.injectionMetadataCache.get(cacheKey); if (InjectionMetadata.needsRefresh(metadata, clazz)) { if (metadata!=null) { metadata.clear(pvs); } metadata=buildResourceMetadata(clazz); this.injectionMetadataCache.put(cacheKey, metadata); } } } returnmetadata; }
可以看到,方法最终返回的metadata
,是通过buildResourceMetadata
方法根据参数传入的类型信息clazz
构建的,构建一次后,会放到当前后处理器的injectionMetadataCache
中缓存,之后需要这些信息的地方,只需要从缓存中读取即可。
接下来,我们进入buildResourceMetadata
方法,看具体的构建流程。
构建注解元信息的 buildResourceMetadata 方法
代码比较长,我们先分析整体结构。省略掉中间do-while循环中的逻辑,我们可以得到以下的结构。
privateInjectionMetadatabuildResourceMetadata(finalClass<?>clazz) { if (!AnnotationUtils.isCandidateClass(clazz, resourceAnnotationTypes)) { returnInjectionMetadata.EMPTY; } List<InjectionMetadata.InjectedElement>elements=newArrayList<>(); Class<?>targetClass=clazz; do { finalList<InjectionMetadata.InjectedElement>currElements=newArrayList<>(); // 省略中间的处理逻辑elements.addAll(0, currElements); targetClass=targetClass.getSuperclass(); } while (targetClass!=null&&targetClass!=Object.class); returnInjectionMetadata.forElements(elements, clazz); }
首先,会判断当前的类型是不是候选的类型,如果不是,则直接返回空的注解元信息InjectionMetadata.EMPTY
。这里用到了一个resourceAnnotationTypes
集合,里面包含了这个方法要处理的注解类型,它的初始化在后处理器的静态代码块中。
static { webServiceRefClass=loadAnnotationType("javax.xml.ws.WebServiceRef"); ejbClass=loadAnnotationType("javax.ejb.EJB"); resourceAnnotationTypes.add(Resource.class); if (webServiceRefClass!=null) { resourceAnnotationTypes.add(webServiceRefClass); } if (ejbClass!=null) { resourceAnnotationTypes.add(ejbClass); } }
由此可以看到,resourceAnnotationTypes
包含了@Resource
注解,除此之外,还会根据当前的运行环境添加@
WebServiceRef
和@
EJB
注解。我们之后的分析只考虑@Resource
注解,另外领个注解的处理逻辑并没有太大差异。
再回到buildResourceMetadata
方法的结构中,参数重传入的类型信息clazz
会被赋值给变量targetClass
,另外还声明了一个elements
集合,存放 InjectedElement 类型的元素。
在do-while
循环体中,每次循环会声明一个currElements
集合变量,类型与elements
相同,在循环体的结尾,currElements
中的元素会被elements
中,并且targetClass
会指向其父类。循环会在targetClass
为空或者为 Object 类型时终止。
可以得出结论,do-while
循环会从clazz
开始,遍历其继承关系中除 Object 以外所有的类型,并从中解析出需要注入的元素,也就是 InjectedElement 集合,最后放到elements
中。
方法的最后,将elements
和类型信息封装为一个 InjectionMetadata 作为结果返回。
接下来我们看循环体中间具体的处理逻辑。
ReflectionUtils.doWithLocalFields(targetClass, field-> { // 省略 @WebServiceRef 和 @EJB 相关的处理逻辑elseif (field.isAnnotationPresent(Resource.class)) { if (Modifier.isStatic(field.getModifiers())) { thrownewIllegalStateException("@Resource annotation is not supported on static fields"); } if (!this.ignoredResourceTypes.contains(field.getType().getName())) { currElements.add(newResourceElement(field, field, null)); } } });
首先是对当前类型所有属性的判断,再次要对属性判断三个条件。
- 属性需要被
@
Resource
注解标记。 - 属性不能是静态的。
- 属性类型不在
ignoredResourceTypes
集合中
符合条件的属性信息会被封装成 ResourceElement 对象并添加到currElements
集合中。
接下来是对方法的处理。
ReflectionUtils.doWithLocalMethods(targetClass, method-> { MethodbridgedMethod=BridgeMethodResolver.findBridgedMethod(method); if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { return; } if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { // 省略 @WebServiceRef 和 @EJB 相关的处理逻辑elseif (bridgedMethod.isAnnotationPresent(Resource.class)) { if (Modifier.isStatic(method.getModifiers())) { thrownewIllegalStateException("@Resource annotation is not supported on static methods"); } Class<?>[] paramTypes=method.getParameterTypes(); if (paramTypes.length!=1) { thrownewIllegalStateException("@Resource annotation requires a single-arg method: "+method); } if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) { PropertyDescriptorpd=BeanUtils.findPropertyForMethod(bridgedMethod, clazz); currElements.add(newResourceElement(method, bridgedMethod, pd)); } } } });
对于当前类型中的每一个方法,首先要确保是代码中定义的方法而非编译器生成的方法,然后是对方法信息的判断条件。
- 方法被
@
Resource
注解标记。 - 方法不是静态的。
- 方法的必须有且只有一个参数。
- 方法的参数类型名称不在
ignoredResourceTypes
集合中。
符合条件的方法信息,也会被封装成一个 ResourceElement 对象,并添加到currElements
集合中。
至此,buildResourceMetadata
方法就结束了。回到postProcessMergedBeanDefinition
方法中,我们看最后一行代码。
注解元信息的检查
metadata.checkConfigMembers(beanDefinition);
此处的metadata
就是刚刚buildResourceMetadata
执行的结果,我们进入checkConfigMembers
方法中查看源码。
publicvoidcheckConfigMembers(RootBeanDefinitionbeanDefinition) { Set<InjectedElement>checkedElements=newLinkedHashSet<>(this.injectedElements.size()); for (InjectedElementelement : this.injectedElements) { Membermember=element.getMember(); if (!beanDefinition.isExternallyManagedConfigMember(member)) { beanDefinition.registerExternallyManagedConfigMember(member); checkedElements.add(element); if (logger.isTraceEnabled()) { logger.trace("Registered injected element on class ["+this.targetClass.getName() +"]: "+element); } } } this.checkedElements=checkedElements; }
逻辑比较清晰,遍历注入元信息中的所有成员,也就是之前处理过的属性和方法信息,对于没有注册为 BeanDefinition 的外部管理配置成员的元素进行注册,并添加到元信息的checkedElements
集合中。这一步主要是为了防止重复处理通过注解注入的元素。
小结
到这里,postProcessMergedBeanDefinition
方法的源码就分析完了,它主要完成的工作就是解析类型中被@
Resource
注解标记并且符合条件的属性和方法,将他们封装成为注解元信息,供后续的流程中使用。
接下来看postProcessProperties
方法
postProcessProperties 方法和 @Resource 注解的注入
postProcessProperties
方法在 Spring 对 Bean 实例进行属性注入之前被调用,CommonAnnotationBeanPostProcessor 中的postProcessProperties
方法的作用是完成上一步postProcessMergedBeanDefinition
方法中解析到的属性和方法的值注入。
我们进入方法的源码。
// org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#postProcessPropertiespublicPropertyValuespostProcessProperties(PropertyValuespvs, Objectbean, StringbeanName) { InjectionMetadatametadata=findResourceMetadata(beanName, bean.getClass(), pvs); try { metadata.inject(bean, beanName, pvs); } catch (Throwableex) { thrownewBeanCreationException(beanName, "Injection of resource dependencies failed", ex); } returnpvs; }
首先调用findResourceMetadata
方法获取了注解元信息,这个方法前文中已经介绍过,这次在调用这个方法,可以直接从缓存集合中获取到注解元信息。之后就比较简单了,通过调用注解元信息的inject
方法,对参数传入的pvs
变量进行处理,也就是进行属性值的注入,最后再将处理完的pvs
返回即可。
其实这里的处理逻辑和之前的一篇文章中介绍的@
Autowired
等注解的注入逻辑基本是相同的,详细的流程可以参考之前那篇文章。
传送门:Spring 源码阅读 42:AutowiredAnnotationBeanPostProcessor 分析(3)
这里我们还是简单回顾一下整个流程,进入 InjectionMetadata 的inject
方法中。
// org.springframework.beans.factory.annotation.InjectionMetadata#injectpublicvoidinject(Objecttarget, StringbeanName, PropertyValuespvs) throwsThrowable { Collection<InjectedElement>checkedElements=this.checkedElements; Collection<InjectedElement>elementsToIterate= (checkedElements!=null?checkedElements : this.injectedElements); if (!elementsToIterate.isEmpty()) { for (InjectedElementelement : elementsToIterate) { if (logger.isTraceEnabled()) { logger.trace("Processing injected element of bean '"+beanName+"': "+element); } element.inject(target, beanName, pvs); } } }
将checkedElements
或者injectedElements
作为要处理的元素集合,赋值给elementsToIterate
并进行遍历,然后调用每一个注入元素element
的inject
方法。这里的注入元素,就是之前步骤中找到的每一个属性或者方法的信息。
我们在进入到 InjectedElement 的inject
方法中。
// org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement#injectprotectedvoidinject(Objecttarget, StringrequestingBeanName, PropertyValuespvs) throwsThrowable { if (this.isField) { Fieldfield= (Field) this.member; ReflectionUtils.makeAccessible(field); field.set(target, getResourceToInject(target, requestingBeanName)); } else { if (checkPropertySkipping(pvs)) { return; } try { Methodmethod= (Method) this.member; ReflectionUtils.makeAccessible(method); method.invoke(target, getResourceToInject(target, requestingBeanName)); } catch (InvocationTargetExceptionex) { throwex.getTargetException(); } } }
这里的逻辑就是,从 Spring 容器中找到要注入的 Bean 实例,通过给属性复制,或者使用找到的 Bean 实例作为参数调用方法的方式,进行注入操作。
此时,注入的工作就完成了。
总结
本文分析了 CommonAnnotationBeanPostProcessor 后处理器处理@
Resource
等注解的解析和注入的原理。下一篇讲介绍 Spring 通过 CommonAnnotationBeanPostProcessor 对另外两个 JSR-250 注解@PostConstruct
和@PreDestroy
的支持。