一个属性同时使用Autowired和Resource注解会发生什么?

baojunzh
发布于 2022-10-26 11:25
浏览
0收藏

如题,如果在同一个属性上使用@Autowired注解注入bean1,然后使用@Resource注解注入bean2会发生什么?

先给出几个猜想:
1.报错,不能重复注入。
2.先注入bean1再注入bean2,类似于map中put同一个key覆盖value。
3.注入bean1。Spring注入前判断属性注入过不再重复注入,且先处理@Autowired
4.注入bean2。Spring注入前判断属性注入过不再重复注入,且先处理@Resource

测试验证

首先定义一个OrderService,beanName为orderService,desc属性默认是default

@Component("orderService")
public class OrderService{
   private String desc = "default";
   //getter  setter toString
}

然后使用@Bean的方式再注册一个OrderService,beanName为orderService1,desc属性赋值为update

@Bean("orderService1")
public OrderService orderService1() {
    OrderService orderService = new OrderService();
    orderService.setDesc("update");
    return orderService;
}

然后我们在UserService的orderService属性上同时加上@Autowired注解(会注入beanName为orderService的实例),和@Resource(name = "orderService1")注解,即指定注入name为orderService1的bean。

@Component
public class UserService{
   @Autowired
   @Resource(name = "orderService1")
   private OrderService orderService;

   public void test() {
      System.out.println("test:"+orderService);
   }
}

然后我们在测试类中,从Spring容器中获取UserService,最终UserService持有的是哪个bean?

public static void main(String[] args) {
   AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
   UserService userService = (UserService) context.getBean("userService");
   userService.test();
}

控制台输出:

test:OrderService{desc='update'}

可见,UserService中 OrderService属性注入的是orderService1。调整属性上注解的顺序,控制台输出依旧如此,说明和注解顺序没有关系。这和我们的猜想2相同,即Spring先处理@Autowired注解注入orderService,再处理@Resource注解注入orderService1,从而覆盖了先注入的。我们这个结论对不对呢?往下看,从源码中找答案比较靠谱。

注入流程

在看源码之前,先看一下Spring注入流程。在Spring框架中,Bean的属性注入可以使用@Autowired注解也可以使用@Resource注解。

Autowired

@Autowired注解是Spring框架提供的,可以写在:

  • 属性上:先根据属性类型去找Bean,如果找到多个再根据属性名确定一个。
  • 构造方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个。
  • set方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个。

总体就是@Autowired先基于类型去找Bean,如果找到多个Bean,再根据name确定一个。

Resource

@Resource注解是Java提供的,可以用在方法上、属性上,它的注入流程是:
1、先判断BeanFactory中是否存在注入点名字(属性名字或方法参数名字)对应的Bean,
2、如果存在则只会根据注入点名字(属性名字或方法参数名字)去找bean,如果找不到对应的bean会报错。
3、如果不存在,再去判断@Resource注解中是否指定了name属性,
4、如果指定了,则只会根据name去找bean,如果找不到对应的bean会报错。
5、如果没有指定,则会和@Autowired注解一样,先byType再byName。

一个属性同时使用Autowired和Resource注解会发生什么?-鸿蒙开发者社区

依赖注入源码分析

在Bean创建过程中,会对Bean的属性进行赋值,即依赖注入,Spring是怎么实现的呢?

我们都知道spring在创建bean的过程中有很多的扩展点,说白了就是留了很多接口,在创建bean的过程调用这些接口的方法,通过实现这些接口嵌入自定义的处理逻辑,从而完成很多功能。Spring的依赖注入也不例外。

先剧透一下,Spring中​CommonAnnotationBeanPostProcessor​​、​AutowiredAnnotationBeanPostProcessor​​实现了​InstantiationAwareBeanPostProcessor​​的​postProcessProperties​方法,完成@Resource、@Autowired的依赖注入。

如下两张图为​CommonAnnotationBeanPostProcessor​​、​AutowiredAnnotationBeanPostProcessor​的局部类图。

一个属性同时使用Autowired和Resource注解会发生什么?-鸿蒙开发者社区

一个属性同时使用Autowired和Resource注解会发生什么?-鸿蒙开发者社区



具体流程如下:

1、属性填充。

Spring实例化Bean后,调用populateBean方法对Bean进行属性填充。

在​AbstractAutowireCapableBeanFactory​​的​doCreateBean​​方法中创建Bean实例,之后,调用同一个类中的​populateBean​方法做属性填充,即处理@Autowired注解和@Resource注解。

关键代码如下:

for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
   // 这里会调用AutowiredAnnotationBeanPostProcessor的postProcessProperties()方法,会直接给对象中的属性赋值
   // AutowiredAnnotationBeanPostProcessor内部并不会处理pvs,直接返回了
   PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
   if (pvsToUse == null) {
      if (filteredPds == null) {
         filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
      }
      pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
      if (pvsToUse == null) {
         return;
      }
   }
   pvs = pvsToUse;
}

2、属性注入。

调用​InstantiationAwareBeanPostProcessor​​的​postProcessProperties​方法完成属性注入。

上述代码通过​getBeanPostProcessorCache().instantiationAware​​获取所有​InstantiationAwareBeanPostProcessor​​的实现类,循环并调用其​postProcessProperties​方法。

通过debug我们会发现,​getBeanPostProcessorCache().instantiationAware​​获取到到的实现类​CommonAnnotationBeanPostProcessor​​在实现类​AutowiredAnnotationBeanPostProcessor​的前面,如下图所示:

一个属性同时使用Autowired和Resource注解会发生什么?-鸿蒙开发者社区

这就意味着Spring先处理@Resource后处理@Autowired,说明我们前面的猜想是错误的。所以,Spring是先处理@Resource的对属性注入,再处理@Autowired,发现属性已经被注入了,就不再重复注入?是不是这样我们继续看源码。
​​CommonAnnotationBeanPostProcessor​​和​AutowiredAnnotationBeanPostProcessor​​的​postProcessProperties​​方法非常相似,​AutowiredAnnotationBeanPostProcessor​​的​postProcessProperties​​方法源码如下,只比​CommonAnnotationBeanPostProcessor​​的多了一个​BeanCreationException​的catch。

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
   // 找注入点(所有被@Autowired注解了的Field或Method)
   InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
   try {
      //注入
      metadata.inject(bean, beanName, pvs);
   }catch (BeanCreationException ex) {
      throw ex;
   }catch (Throwable ex) {
      throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
   }
   return pvs;
}

findAutowiringMetadata​​的逻辑是从​injectionMetadataCache​​(map,缓存注入点元数据)中获取​InjectionMetadata​​,这里从缓存map中能够获取到​InjectionMetadata​​,然后调用其inject方法完成属性注入。​CommonAnnotationBeanPostProcessor​​的​findAutowiringMetadata​​逻辑也是如此。
我们看下​​InjectionMetadata​​的​inject​方法

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
   Collection<InjectedElement> checkedElements = this.checkedElements;
   Collection<InjectedElement> elementsToIterate = (checkedElements != null ? checkedElements : this.injectedElements);
   if (!elementsToIterate.isEmpty()) {
      // 遍历每个注入点进行依赖注入
      for (InjectedElement element : elementsToIterate) {
         element.inject(target, beanName, pvs);
      }
   }
}

该方法遍历注入点集合​elementsToIterate​​,调用inject方法完成属性注入,只不过​elementsToIterate​​的取值有点小逻辑,如果​checkedElements​​不为null就取​checkedElements​​,否则取​injectedElements​​。
对于刚刚我们的猜想,会不会是​​elementsToIterate​​没有元素,所以​AutowiredAnnotationBeanPostProcessor​​属性注入直接跳过了,所以注入的是@Resource注解的?而且,我们还没看到​checkedElements​​和​injectedElements​是在哪赋值的。

3、寻找注入点。

通过源码可以看到,实例化Bean之后,调用​populateBean​​方法属性填充之前调了一个本类的方法​applyMergedBeanDefinitionPostProcessors

protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
   for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) {
      processor.postProcessMergedBeanDefinition(mbd, beanType, beanName);
   }
}

CommonAnnotationBeanPostProcessor​​和​AutowiredAnnotationBeanPostProcessor​也实现了相应的接口

一个属性同时使用Autowired和Resource注解会发生什么?-鸿蒙开发者社区

来看看它们的实现

//CommonAnnotationBeanPostProcessor
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
   super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
   InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null);
   metadata.checkConfigMembers(beanDefinition);
}
//AutowiredAnnotationBeanPostProcessor
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
   InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
   metadata.checkConfigMembers(beanDefinition);
}

它们都调用了​findAutowiringMetadata​​方法寻找注入点,包装为​InjectionMetadata​​对象并将注入点集合赋值给​injectedElements​​属性,然后将​InjectionMetadata​​对象放到缓存map中,所以在​postProcessProperties​​中调用​findAutowiringMetadata​​可以从缓存map中获取到​InjectionMetadata​。

4、重复的注入点不会重复注入。

重复的注入点不会添加到​checkedElements​中。

还都调用了​checkConfigMembers​​方法,上一个方法​findAutowiringMetadata​​完成了​injectedElements​​属性的赋值,那么这个方法是对​checkedElements​方法进行赋值的。

public void checkConfigMembers(RootBeanDefinition beanDefinition) {
   Set<InjectedElement> checkedElements = new LinkedHashSet<>(this.injectedElements.size());
   for (InjectedElement element : this.injectedElements) {
      Member member = element.getMember();
      // 将Field或Method记录到BeanDefinition中的externallyManagedConfigMembers中,表示该Field或Method是BeanFactory外部管理的
      if (!beanDefinition.isExternallyManagedConfigMember(member)) {
         beanDefinition.registerExternallyManagedConfigMember(member);
         checkedElements.add(element);
      }
   }
   this.checkedElements = checkedElements;
}

这个方法的大致逻辑是将​injectedElements​​的元素,放到checkedElements中, 只有满足条件的才放到​checkedElements​​中,判断条件是不在​RootBeanDefinition#externallyManagedConfigMembers​​集合中,把元素添加到该集合和​checkedElements​中。

由于​getBeanPostProcessorCache().mergedDefinition​​获取到的实现类,​CommonAnnotationBeanPostProcessorAutowiredAnnotationBeanPostProcessor前面。

所以对于同一个属性,第二次判断时

!beanDefinition.isExternallyManagedConfigMember(member)

条件不成立,元素就不会放到checkedElements

AutowiredAnnotationBeanPostProcessor在执行postProcessProperties方法时,调用InjectionMetadatainject方法,此时checkedElements不会有该注入点,就不会重复注入。

结论

通过源码了解到,并不是单纯的处理顺序的先后被覆盖的原因,依赖注入是创建Bean实例后完成的,通过实现​MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition​​寻找注入点放入缓存中,再通过实现​InstantiationAwareBeanPostProcessor#postProcessProperties​​从缓存中获取注入点,完成属性注入。其中重复的注入点不会重复放到缓存中,所以先执行的​CommonAnnotationBeanPostProcessor​​会完成属性注入,后执行的​AutowiredAnnotationBeanPostProcessor​自然不会再注入。

便于理解流程,画了一张图:

一个属性同时使用Autowired和Resource注解会发生什么?-鸿蒙开发者社区


本文转载自公众号biggerboy

标签
已于2022-10-26 11:25:44修改
收藏
回复
举报
回复
    相关推荐