前言
阅读这边文章之前我们先保持这样一个疑问 : @Resource注解真的是根据名称注入的么?
@Resource注解如何使用
代码准备
创建一个接口,以及三个实现其接口的bean
public interface Animal {
}
@Component
public class Cat implements Animal {
}
@Component
public class Dog implements Animal {
}
@Component
public class Fish implements Animal {
}
创建一个类型为House的bean
@Component
public class House {
}
创建配置文件resource.properties
animal=fish
创建配置类
@ComponentScan("com.test.resource")
@PropertySource("classpath:resource.properties")
public class AppConfig {
}
创建启动类
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
House bean = context.getBean(House.class);
System.out.println(bean);
}
}
作用属性上
单体依赖指定name (name和beanName一致)
@Component
public class House {
@Resource(name = "dog")
private Dog dog;
}
单体依赖指定name (name和beanName不一致)
@Component
public class House {
@Resource(name = "dog1")
private Dog dog;
}
单体依赖不指定name
@Component
public class House {
@Resource
private Dog dog;
@Resource
private Dog dog1;
}
单体依赖指定name(EL表达式)
@Component
public class House {
@Resource(name = "${animal}")
private Fish fish;
}
集合不指定name (属性名与所有beanName不一致)
@Component
public class House {
@Resource
private List<Animal> animals;
}
集合不指定name (属性名和其中一个beanName一致)
@Component
public class House {
@Resource
private List<Animal> fish;
}
集合指定name (name与所有beanName不一致)
@Component
public class House {
@Resource(name = "animals")
private List<Animal> animals;
}
集合指定name (name和其中一个beanName一致)
@Component
public class House {
@Resource(name = "fish")
private List<Animal> animals;
}
作用于方法上
方法指定name (name和beanName一致)
@Component
public class House {
private Cat cat;
@Resource(name = "cat")
public void setCat(Cat cat) {
this.cat = cat;
}
}
方法指定name (name和beanName不一致)
@Component
public class House {
private Cat cat;
@Resource(name = "cat1")
public void setCat(Cat cat) {
this.cat = cat;
}
}
方法不指定name
public class House {
private Cat cat;
private Cat cat1;
@Resource
public void setCat(Cat cat) {
this.cat = cat;
}
@Resource
public void setCat1(Cat cat1) {
this.cat1 = cat1;
}
}
如果参数类型是基类,则@Resource的name必须和其中一个子类的beanName一致或者方法名和其中一个子类的beanName一致
name和beanName一致
@Component
public class House {
private Animal animal;
@Resource(name = "fish")
public void setAnimal(Animal animal) {
this.animal = animal;
}
}
方法名和beanName一致
@Component
public class House {
private Animal animal;
@Resource
public void setDog(Animal animal) {
this.animal = animal;
}
}
小结
我们通过观察发现,其实作用在属性上和作用在方法上是一样的。作用在方法上的时候,我们可以把参数当做属性的类型,把方法名当成属性名。
源码分析
前置知识
1.处理@Resource的后置处理器是CommonAnnotationBeanPostProcessor
2.各个后置处理器会在bean的实例化过程中执行特定的方法,执行顺序如下
- postProcessBeforeInstantiation
- postProcessMergedBeanDefinition : 发现注入点
- postProcessAfterInstantiation
- postProcessProperties : 属性注入
- postProcessBeforeInitialization
- postProcessAfterInitialization
在上面的前提下我们开始分析
1.发现注入点
CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
CommonAnnotationBeanPostProcessor#findResourceMetadata
CommonAnnotationBeanPostProcessor#buildResourceMetadata
由上述源码我们得出结论
如果field或者method上存在@Resource注解,我们就可以将其当做一个注入点
注意点
- field或者method不能是static的
- method的参数有且仅有一个
注入点的类型是ResourceElement,我们看一下其构造方法
public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
super(member, pd);
Resource resource = ae.getAnnotation(Resource.class);
// 获取我们指定的name
String resourceName = resource.name();
Class<?> resourceType = resource.type();
// 如果我们指定了name,则isDefaultName为false
// PS:isDefaultName这个属性很重要,重点关注一下
this.isDefaultName = !StringUtils.hasLength(resourceName);
if (this.isDefaultName) {
// 如果未指定name,resourceName就是属性名
resourceName = this.member.getName();
// 如果member是一个Method,并且方法名以set开头,并且方法名的长度大于3,则进行相应处理,大概有下列几种方法
// setAbc ==> abc
// setABc ==> ABc
// setABC ==> ABC
// abc => abc
if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
resourceName = Introspector.decapitalize(resourceName.substring(3));
}
}
// 正常情况是beanFactory是存在embeddedValueResolver的,所有如果我们将name指定成${xx}这样的形式也是会解析的
// PS:如果不存在beanFactory会添加一个默认的embeddedValueResolver
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());
}
2.属性注入
CommonAnnotationBeanPostProcessor#postProcessProperties
InjectionMetadata#inject
InjectedElement#inject
ResourceElement#getResourceToInject
CommonAnnotationBeanPostProcessor#getResource
CommonAnnotationBeanPostProcessor#autowireResource
protected Object autowireResource(BeanFactory factory, CommonAnnotationBeanPostProcessor.LookupElement element, @Nullable String requestingBeanName)
throws NoSuchBeanDefinitionException {
Object resource;
Set<String> autowiredBeanNames;
String name = element.name;
if (factory instanceof AutowireCapableBeanFactory) {
AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
DependencyDescriptor descriptor = element.getDependencyDescriptor();
// fallbackToDefaultTypeMatch这个值默认是true,暂不讨论
// isDefaultName这个属性就很重要了,如果这个值为false,则表明我们指定了name,不会进入这个if分支
// if分支和else分支最大的区别就是,if分支返回的结果可能是一个集合,而else分支只可能返回单个bean
// 一般的beanName为类名的驼峰形式,所以我们属性名也很重要,如果与beanName一致则进入else分支,否则进入if分支
if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
autowiredBeanNames = new LinkedHashSet<>();
resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
if (resource == null) {
throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
}
}
else {
resource = beanFactory.resolveBeanByName(name, descriptor);
autowiredBeanNames = Collections.singleton(name);
}
}
else {
resource = factory.getBean(name, element.lookupType);
autowiredBeanNames = Collections.singleton(name);
}
if (factory instanceof ConfigurableBeanFactory) {
ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) factory;
for (String autowiredBeanName : autowiredBeanNames) {
if (requestingBeanName != null && beanFactory.containsBean(autowiredBeanName)) {
beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);
}
}
}
return resource;
}
我们主要关注的是,根据我们的设置,Spring是执行的resolveDependency方法,还是执行resolveBeanByName方法。执行resolveDependency可能会返回一个集合,执行resolveBeanByName最多有一个bean
PS : resolveDependency方法解析流程可以看我上篇博文 : Spring之@Autowired注解
我们画一个流程图,演示上述源码流程,也可以用上文中@Resource的使用案例来一一验证
@Resource是根据类型查找bean?
我们在上文的分析中得出,如果注入点是一个集合类,未指定name,并且属性名在不和任意一个beanName一致的情况下,是可以根据类型查找出多个bean的情况的,所以@Resource不完全是根据名称查找bean的
我们看一下resolveBeanByName方法细节
执行resolveBeanByName方法,会在方法传递指定了requiredType,如果requiredType不为null,Spring会执行adaptBeanInstance方法,来校验bean的类型与requiredType的类型是否一致,再次证明了@Resource注解不是完全根据名称查找bean,如果完全是根据名称查找bean,则不应该传递requiredType来校验bean的类型