@Resource注入的源码解析
不愿看这段可跳过系列:
话是针对可能是刷到这篇博客的人写的(而非订阅从头到尾跟着去阅读的):
还是那样说,源码阅读分析肯定不是一步到位的,而且上下都是有一定关联的。举个例子:咱若是不知道
createBean->doCreateBean 的流程,又怎会真正知道 Spring Bean的生命周期,又怎么会知道实例化后会遍历所有的 MergedBeanDefinitionPostProcessor,
处理后才会到属性注入,又怎会知道我们开发常用的注解式注入开发也是InstantiationAwarePostProcessor在作祟,而Spring提供的手动或者BYNAME或者BYTYPE注入咱开发也不带用的似乎,又怎么知道注入的属性在构造函数中使用会出问题,又怎么知道后续复用的方法的实现逻辑…当然分析源码所耗的精力和所得肯定是不成正比的,肯定收获会更小点,看个人吧~
阅读此需阅读下面这些博客先 |
---|
【Spring源码分析】Bean的元数据和一些Spring的工具 |
【Spring源码分析】BeanFactory系列接口解读 |
【Spring源码分析】执行流程之非懒加载单例Bean的实例化逻辑 |
【Spring源码分析】从源码角度去熟悉依赖注入(一) |
【Spring源码分析】从源码角度去熟悉依赖注入(二) |
一、绪论
前面提到的 @Autowired、@Value
属性注入是在 AutowiredAnnotationBeanPostProcessor
中进行实现的。而我们现在要提的 @Resource 注解实现的属性注入是在 CommonAnnotationBeanPostProcessor
中实现的,下面是它的结构图:
可以看见也实现了 InstantiationAwareBeanPostProcessor
和 MergedBeanDefinitionPostProcessor
。(所以说要我说都是源码都是一步一步的,而且Spring生命周期流程要记于心,像这 MergedBeanDifinitionPostProcessor 就是在实例化的后面,属性注入前的实例化后的前面进行的)
除了上面那些咱还需要了解 @Resource
注解不是Spring所提供的,是 jdk 里的一个注解。全限定类名是 javax.annotation.Resource
。这样做主要是为了如果代码切换成别的可以依赖注入的框架的话,也支持了 @Resource 注解的使用,就不用大幅度去修改源码了。
好了没啥需要解释的了,开始分析源码。
二、CommonAnnotationBeanPostProcessor源码分析
首先咱先看看它类加载初始化做了啥(就是把 @Resource 放到 resourceAnnotationTypes 集合中):
那咱接下来就按生命周期的流程去分析这个源码:遍历MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition->实例化后,遍历的InstantiationAwarePostProcessor#postProcessAfterInstantiation->属性注入,遍历的InstantiationAwarePostProcessor#postProcessProperties
可以看见实例化后没有写任何操作的,所以我们只需要关心前一步和属性注入那一步,接下来来分析咯~
postProcessMergedBeanDefinition 源码解析
这里的寻找注入点和上一篇的 AutowireAnnotationBeanPostProcessor 其实一样的,不信看(就是循环父类,然后遍历所有字段和方法,把带有@Resource注解的当做注入点,实例化为ResourceElement
对象为注入点):
postProcessProperties 源码解析
寻找注入点逻辑就不用说了,咱主要看是如何注入的:
注入的话就是遍历去遍历每个InjectedElement然后调用其inject方法,我们的InjectElement上面说了是 ResourceElement
。
我们可以看见 InjectElement
的 inject
方法实现就俩个地方实现了:
就是说 ResourceElement 的注入逻辑其实就是 InjectElement#inject。
在分析具体的注入逻辑时我们需要关注下ResourceElement构造的时候做了些啥:
ResourceElement构造函数分析
在构造中会对@Resource进行一些获取,对ResourceElement进行一定的初始化:
public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
super(member, pd);
Resource resource = ae.getAnnotation(Resource.class);
String resourceName = resource.name();
Class<?> resourceType = resource.type();
// 使用@Resource时没有指定具体的name,那么则用field的name,或setXxx()中的xxx
// 这个后续会遇到的
// 如果注解上没有指定name,则isDefaultName就是true
this.isDefaultName = !StringUtils.hasLength(resourceName);
// 如果没有指定name,那么就以属性名或者方法名为主
if (this.isDefaultName) {
resourceName = this.member.getName();
if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
resourceName = Introspector.decapitalize(resourceName.substring(3));
}
}
// 使用@Resource时指定了具体的name,进行占位符填充
// 支持${}
else if (embeddedValueResolver != null) {
resourceName = embeddedValueResolver.resolveStringValue(resourceName);
}
// @Resource除开可以指定bean,还可以指定type,type默认为Object
if (Object.class != resourceType) {
// 如果指定了type,则验证一下和field的类型或set方法的第一个参数类型,是否和所指定的resourceType匹配
checkResourceType(resourceType);
}
else {
// No resource type specified... check field/method.
resourceType = getResourceType();
}
this.name = (resourceName != null ? resourceName : "");
this.lookupType = resourceType;
String lookupValue = resource.lookup();
this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
Lazy lazy = ae.getAnnotation(Lazy.class);
this.lazyLookup = (lazy != null && lazy.value());
}
主要经过了下面几个操作:
- 如果@Resource中设置了name属性,那么
isDefaultName
就为 false,否则以参数和方法参数名为主;如果 isDefaultName 为false的话,也会使用embeddedValueResolver
对其进行解析,前面阐述过就是解析${}
,再说白点就是从 Environment 中获取对应Value值。也就是一般这种情况下,我们才会去配置name去。
其他的也没啥好解释的啦,字面意思。
inject 方法解析
看见注入的方法其值都是从getResourceToInject
获取到的值,下面就阐述核心吧,方法调用来调用去的核心就那么几行。
- 首先是 @Resource 中 name 属性没有去自定义值且属性名或参数名在容器中是存在的的话就去根据name直接去调用getBean。
- 否则直接根据类型去找对应的bean。
BeanFactory#resolveDependency
。这个方法在之前都阐述出了。
如果我们在 @Resource 注解中设置了 name 属性值,那么这判断就直接是 false 了,就直接会进行name注入。
三、总结
- @Resource 注解是 jdk 中的,不是 Spring 的;
- @Resource 和 @Autowire 代码性能上大差不差;
- @Resource 中的name要是赋值了就只以 name 去查询对应的Bean,不会再根据类型去找了。
- @Resource 中的 name 要是赋值了,那么会经过一次解析,也就是name里面可以使用
${}
。 - 若 @Resource 中name没有赋值的话,那么就是咱们常说的:先根据属性名或者参数名进行注入,再根据类型进行注入,就是那个 BeanFactory#resolveDependency 那个方法。
这是流程图: