@Resource详解

@Resource详解

阅读本文之前希望读者最好已经对整个Bean的大体Spring执行顺序已经有了一定的了解。

示例

定义一个接口,表示水果类,只包含一个方法代表售卖。

public interface Fruit {

    void sell();
}

有两个具体实现类,Apple🍎和Banana🍌。

@Service
public class Apple implements Fruit {


    public Apple() {
        System.out.println("Apple......");
    }

    @Override
    public void sell() {
        System.out.println("苹果2元一斤");
    }
}
@Service
public class Banana implements Fruit {

    public Banana() {
        System.out.println("Banana...");
    }

    @Override
    public void sell() {
        System.out.println("香蕉3元一串");
    }
}

具体的商店类来注入Fruit接口。

@Component
public class Store {

    @Resource
    private Fruit fruit;

    public void getFruit() {
        fruit.sell();
    }

}

测试方法:

public class Test {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        context.getBean(Store.class).getFruit();


        // 优雅关闭,SpringWEB中已有相关实现
        context.registerShutdownHook();
    }
}

实际执行结果:
在这里插入图片描述

这里可以清楚的看到报了NoUniqueBeanDefinitionException异常,说是希望单个Bean的匹配,却找到了多个。

下面就来具体的讲下为什么。

首先,@Resource中没有设置任何属性值,统统采用的是默认的值。

按照Spring Bean的加载顺序,Store Bean创建的时候,BeanFactory中已经创建了Apple和Banana Bean。

在这里插入图片描述

1. newInstance

第一步就是先创建出Store对象。

2. 解析类中的字段

Spring在实例化对象后,会调用 applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);方法对BeanDefinition进行完善,主要为了后续的BeanPostProcessor处理器在注入对应的字段时能获取到需要注入的类的相关信息。

而对应需要注入的一个个类而言,就是使用ResourceElement对象来进行保存,相关重要的构造函数如下:

public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
   super(member, pd);
   Resource resource = ae.getAnnotation(Resource.class);
   // 获取@Resource的name属性
   String resourceName = resource.name();
   // 获取@Resource的type属性
   Class<?> resourceType = resource.type();
   this.isDefaultName = !StringUtils.hasLength(resourceName);
   if (this.isDefaultName) {
      // 如果没有设置@Resource name属性就用字段名称作为bean name
      resourceName = this.member.getName();
      // 如果member是setter方法,则取setXXX的XXX部分为bean name
      if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
         resourceName = Introspector.decapitalize(resourceName.substring(3));
      }
   }
   else if (embeddedValueResolver != null) {
      // 如果设置了@Resource name的属性,则使用EmbeddedValueResolver对象先做一次SpringEL解析得到真正的bean name
      resourceName = embeddedValueResolver.resolveStringValue(resourceName);
   }
   if (Object.class != resourceType) {
      // 确保字段或setter方法类型与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();
   // 如果使用jndi查找名字
   this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
   Lazy lazy = ae.getAnnotation(Lazy.class);
   // 是否延迟注入
   this.lazyLookup = (lazy != null && lazy.value());
}

当name属性没有被设置时,就会执行下面的分支,根据是方法注入还是属性注入,分别设置为方法名称set后面的字符串或字段名称。

   if (this.isDefaultName) {
      // 如果没有设置@Resource name属性就用字段名称作为bean name
      resourceName = this.member.getName();
      // 如果member是setter方法,则取setXXX的XXX部分为bean name
      if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
         resourceName = Introspector.decapitalize(resourceName.substring(3));
      }
   }

下面是具体的ResourceElement类对象中的各属性。

在这里插入图片描述

然后可以测试下在@Resource注解中加入name属性;

@Resource(name = “apple”)

得到的是下面的对象。
在这里插入图片描述

这里可以看出name属性会有明显的不同。

这里name属性和lookupType属性其实可以对应于@Resource中的name和type属性。

3. populateBean

第三步就是类属性的注入。

执行到对应的处理器(@Resource是通过CommonAnnotationBeanPostProcessor处理器进行处理的)进行属性注入的时候,autowireResource就会用来获取对应属性需要注入的对象。

protected Object autowireResource(BeanFactory factory, 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();
      // 判断是否设置了name属性的值
      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;
}

这里的element对象就是我们之前解析保存的ResourceElement

然后最重要的,上面强调过的name属性在不同Resource注解中解析出来的ResourceElement是会不同的,这里就会有不同的处理方式。

isDefaultName为false时,说明name属性被设置了值,此时执行的如下逻辑;

     resource = beanFactory.resolveBeanByName(name, descriptor);
     autowiredBeanNames = Collections.singleton(name);

resolveBeanByName方法就是通过设置的名称来进行解析对应的Bean对象。接着就能看到经常使用到的getBean方法,从BeanFactory中拿到Bean对象。

public Object resolveBeanByName(String name, DependencyDescriptor descriptor) {
   InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
   try {
      // 获取bean
      return getBean(name, descriptor.getDependencyType());
   }
   finally {
      // 为目标工厂方法提供依赖描述符
      ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
   }
}

当Resource注解中name没有设置值,即使用的是字段名称作为beanName,执行的就是

         resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);

resolveDependency方法中实际工作的方法就是doResolveDependency:

public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

   //设置新得当前切入点对象,得到旧的当前切入点对象
   InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
   
       // ...省略

      // 查找相关所有类型匹配的bean
      Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
      //如果没有候选bean对象
      if (matchingBeans.isEmpty()) {
         //如果descriptor需要注入
         if (isRequired(descriptor)) {
            //抛出NoSuchBeanDefinitionException或BeanNotOfRequiredTypeException以解决不可 解决的依赖关系
            raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
         }
         //返回null,表示么有找到候选Bean对象
         return null;
      }

      //定义用于存储唯一的候选Bean名变量
      String autowiredBeanName;
      //定义用于存储唯一的候选Bean对象变量
      Object instanceCandidate;

      //如果候选Bean对象Map不止有一个
      if (matchingBeans.size() > 1) {
         //确定candidates中可以自动注入的最佳候选Bean名称
         autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);

         if (autowiredBeanName == null) {
            // 如果有多个匹配结果,抛出异常
            if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
               //让descriptor尝试选择其中一个实例,默认实现是抛出NoUniqueBeanDefinitionException.
               return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
            }
            else {
               return null;
            }
         }
         instanceCandidate = matchingBeans.get(autowiredBeanName);
      }
      else {
         //获取machingBeans唯一的元素
         Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
         autowiredBeanName = entry.getKey();
         instanceCandidate = entry.getValue();
      }

      // ...省略
  
      //返回最佳候选Bean对象【result】
      return result;
   }
   finally {
      //设置上一个切入点对象
      ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
   }
}

doResolveDependency方法中会获取所有和指定type相同的所有bean集合,当发现bean集合中的元素个数超过1时就抛出了上面出现的错误了,如果只有一个那自然而然就是拿那个bean来返回。

这里抛出的异常其实不是直接抛出的,而是调用的DependencyDescriptor中的resolveNotUnique方法,而该方法默认实现就是会直接抛出一个NoUniqueBeanDefinitionException异常,这也是Spring留的一个可扩展的地方。

我们可以通过继承DependencyDescriptor,然后重写该方法来自定义选择一个最优的bean(比如说选择第一个),然后在BeanPostProcessor中选择我们的子类作为实现,这样在@Resource不指定任何属性的情况下,有多个实现类的bean也不会抛出异常。

在这里插入图片描述

拓展

  • 当只指定type属性时;

@Resource(type = Apple.class)
在这里插入图片描述

设置type属性后,isDefaultName的值还是为true,所以执行的还是resolveDependency方法。但是由于添加了类型的限制,所以也就不会匹配到多个Bean,而产生异常。

  • 既指定了name属性,又指定了type类型,但是是不同的类;

@Resource(name = “banana”, type = Apple.class)
在这里插入图片描述

name属性被设置为banana,isDefaultName变为false,执行resolveBeanByName方法。

但是由于找不到对应beanName为banana,但是类型又为Apple.class的bean,还是会抛出异常。

在这里插入图片描述

总结

多种@Resource不同使用情况所执行方法如下所示;

在这里插入图片描述

  • 当@Resource不设置任何值时,isDefaultName会为true,当对应字段名称的bean或者BeanDefinition已存在时会走byName的形式,否则走byType的形式

  • 只指定了type属性时,只有当对应的名称不存在对应的bean或BeanDefinition,才会通过byType找到唯一的一个类型匹配的bean;

  • 只指定了name属性,会执行getBean方法,根据指定的name来获取bean;

  • 既指定了name属性,又指定了type属性,会先根据那么查找对应的bean,然后进行type类型比较。

如何解决本文最上面出现的问题?

  • @Resource中指定name或着type;
  • @Qualifier指定bean名称;
  • 将字段名称修改为指定的bean名称;
  • 直接修改对象类型。

只推荐第一种方法。

  • 81
    点赞
  • 203
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值