引言
在探索原理及源码时,最好是带有目的性的去验证,拿结论来探索过程,往往是最有效的
本次我们来探索一下@Resource注解的工作原理,我们先看下结论
1.如果@Resource注解中指定了name属性,那么则只会根据name属性的值去找bean,如果找不到则报错
2.如果@Resource注解没有指定name属性,那么会先判断当前注入点名字(属性名字或方法参数名字)是不是存在Bean,如果存在,则直接根据注入点名字取获取bean,如果不存在,则会走@Autowired注解的逻辑,会根据注入点类型去找Bean
1.找到所有被@Resource注解的字段和方法,进行筛选
InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
-
判断是否能从缓存中获取到,获取到并且类型匹配则直接返回
-
根据当前bean的class类型,获取所有的注入点
(上图代码第363行的buildResourceMetadata方法)- 1.判断是否有@Resource注解
-
2.获取被@Resource注解的Field,static属性不注入
-
3.获取被@Resource注解的Method,static方法不处理,并且方法的参数只能有一个,不然报错
-
4.筛选出可以被当作注入点的Field或者Method各自封装到 ResourceElement 对象,然后放到一个集合中(下图代码447行)
-
5.循环处理父类,相同处理逻辑,直至Object.class停止(上图代码448行)
-
6.将含有注入点的集合,封装到InjectionMetadata对象中(上图代码452行)
-
7.返回
-
put进缓存injectionMetadataCache中
2.根据返回的注入点,进行循环处理、注入
metadata.inject(bean, beanName, pvs);
element.inject(target, beanName, pvs);
- 由于上述步骤封装了ResourceElement对象,内部构造方法会判断注解上是否有值
- 有值,设置为接下来要寻找的beanName
- 无值,设置属性或方法名为接下来要寻找的默认beanName
- 开始找bean
会根据属性还是方法来分别进行处理
核心方法是 getResourceToInject(target, requestingBeanName)
1.如果是@Lazy,则先返回一个代理对象
2.根据设置的name去BeanFactory找bean
找不到:
1.判断name是否为默认生成的名字(未指定value值,根据字段或方法名默认生成)
2.
①不是默认(即value赋值的名字,具体什么时候设置为默认,往下看),报错;
②是默认生成的名字,则开始根据@Autowired自动装配逻辑,进行byType查找,在进行byName(此时byName已经没有意义,因为之前已经查找但找不到);否则直接报错
@Autowired的工作原理可以点此链接来学习一下
//this.fallbackToDefaultTypeMatch判断是否可以在找不到时根据type进行匹配
//element.isDefaultName 判断是否是默认生成的名字
if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
autowiredBeanNames = new LinkedHashSet<>();
//根据name找不到bean时,会调用下面方法,即走@Autowired装配步骤
resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
if (resource == null) {
throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
}
}
找到了: 直接getBean(beanName,target.class),也就是说,根据名字找到了bean,还要再根据类型校验一下
返回
什么是默认生成的name
@Resource(“XXX”)
public UserService userService;
@Rsource如果不指定value值,会根据属性名或者方法名的第一个参数的参数名生成一个默认的name,并且封装的对象属性会设置一个标志(this.isDefaultName),标记该对象的name是默认生成的,还是指定的.
所以如果@Resource注解指定了某个字符串,则会根据指定的字符串去查找,如果根据名字不匹配,则会报错,找不到,而不是继续根据类型匹配
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,那么就使用默认生成的名字
this.isDefaultName = !StringUtils.hasLength(resourceName);
if (this.isDefaultName) {
resourceName = this.member.getName();
if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
resourceName = Introspector.decapitalize(resourceName.substring(3));
}
}
else if (embeddedValueResolver != null) {
resourceName = embeddedValueResolver.resolveStringValue(resourceName);
}
if (Object.class != 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());
}