Spring @Resource 源码解析 – 为什么是ByName注入

前言

上篇博客【@Autowired 源码为什么是ByType注入】跟着源码详细的说明了@Autowired在Spring源码里面是如何设计为byType注入的。本篇博客的主要内容就是源码追踪探究@Resource是为什么被称为byName注入的。更多Spring内容进入【Spring解读系列目录】

构建Sample

为了说明这个流程,我们还是要做一个例子进行追踪。既然要读源码,就必须有一个相应的例子去跟随源码的调试过程查看调用链是怎么走的。首先要有一个接口Demo,然后有两个实现类DemoImpl1DemoImpl2,以及一个DemoService去依赖Demo

@Service
public class DemoService {
    @Resource
    Demo demoImpl1;
}

Spring默认命名规则

为了解读源码更方便一些,简单说下Spring默认的命名规则。Spring默认命名规则很简单,就是把原类名的第一个大写字母替换为小写字母作为这个类的默认实例对象名字,我们在Spring容器的单例对象池中的默认名字就是这个。简单来说就是DemoService会被Spring默认创建一个demoService作为名字的对象。而这个名字也会被当作一个默认的参数做check。

Spring启动调用链

因为这样的追踪过程必须通过调试进行,所以首先还是要把调用流程展示出来。

postProcessProperties:318, CommonAnnotationBeanPostProcessor (org.springframework.context.annotation)
populateBean:1415, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:608, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:531, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:335, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 232307208 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$34)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:333, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:208, AbstractBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingletons:944, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:925, AbstractApplicationContext (org.springframework.context.support)
refresh:588, AbstractApplicationContext (org.springframework.context.support)
<init>:93, AnnotationConfigApplicationContext (org.springframework.context.annotation)
main:9, Test (com.example.test)

通过追踪@Resource的流程,可以明显的看出@Resource使用的后置处理器是CommonAnnotationBeanPostProcessor,而不是@Autowired使用的AutowiredAnnotationBeanPostProcessor。但是前面的流程还是一致的,都是从赋值用的populateBean()方法进入相应的后置处理器的postProcessProperties()方法。

源码流程

既然是走的后置处理方法,那么就直接走到postProcessProperties()中。同样的这里的bean就是DemoService对象,此时DemoService刚刚被实例化出来,正要被Spring容器进行依赖注入。其中的beanName就是根据Spring默认的生成规则生成的"demoService"

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
   InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
   try { //进入这里
      metadata.inject(bean, beanName, pvs);
   }
   catch (Throwable ex) {
      throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
   }
   return pvs;
}
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);  //进入这个inject()方法
      }
   }
}

到目前为止可以看到虽然@Reource@Autowired使用的后置处理器是不一样的,但是其代码是没有什么变化的。真正变化的地方就是在element.inject(target, beanName, pvs)这里。回想一下@Autowired使用的是AutowiredAnnotationBeanPostProcessor内部类的方法AutowiredFieldElement#inject()。但是@Resource使用的则是InjectionMetadata内部类的方法InjectedElement#inject()

protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
      throws Throwable {
   if (this.isField) {
   	  //拿到字段
      Field field = (Field) this.member; 
      ReflectionUtils.makeAccessible(field);
	  //注入
      field.set(target, getResourceToInject(target, requestingBeanName));
   }
   else { /**略**/  }
}

在这个方法里,首先会判断if (this.isField)传入进来的是否是一个字段,拿到字段以后直接对target(DemoService)对象进行字段设置。于是我们需要进入getResourceToInject()方法去查看里面是如何赋值的。

protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
   return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
         getResource(this, requestingBeanName));
}

进入以后,由于不需要设置懒加载,到getResource(this, requestingBeanName)方法里面。

protected Object getResource(LookupElement element, @Nullable String requestingBeanName)
      throws NoSuchBeanDefinitionException {
   if (StringUtils.hasLength(element.mappedName)) {
      return this.jndiFactory.getBean(element.mappedName, element.lookupType);
   }
   if (this.alwaysUseJndiLookup) {
      return this.jndiFactory.getBean(element.name, element.lookupType);
   }
   if (this.resourceFactory == null) {
      throw new NoSuchBeanDefinitionException(element.lookupType,
            "No resource factory configured - specify the 'resourceFactory' property");
   }
   return autowireResource(this.resourceFactory, element, requestingBeanName);
}

碰到几个if语句,看条件的内容,显然是Spring容器自己的类,和Sample都不沾边,直接到return语句的方法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();
      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赋值
         resource = beanFactory.resolveBeanByName(name, descriptor);
         autowiredBeanNames = Collections.singleton(name);
      }
   }
   else {
      resource = factory.getBean(name, element.lookupType);
      autowiredBeanNames = Collections.singleton(name);
   }
   /**判断factory是否是ConfigurableBeanFactory,显然不是略过**/
   return resource;
}

方法进来以后,首先构造返回值对象,下一步自然就是找赋值的地方。往下走可以看到if (factory instanceof AutowireCapableBeanFactory)条件,判断是否是自动注入工厂的类型。很明显这里的factory属于这个类型,进入条件块。碰见下一个if条件,第一个条件没有特别设置默认就是true;第二个条件是否符合默认的命名,显然也是ture;第三个条件就不会成立了,factory里面有这个对象是在Spring容器初始化就完成的,加了非因此就是false。其实这里没有什么关系,因为根据源码来看,如果满足if逻辑,就直接从工厂里拿出来赋值给resource,然后直接返回出去。但是我们当前的逻辑会到else里面,因此还是要走到resolveBeanByName()方法里面。

public Object resolveBeanByName(String name, DependencyDescriptor descriptor) {
   InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
   try { //老熟人儿,getBean()
      return getBean(name, descriptor.getDependencyType());
   }
   finally {
      ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
   }
}

到这个方法里面以后,就能看到一个很熟悉的方法getBean(),之前看过笔者【Spring源码解析AOP(二)】博客的读者,肯定很熟悉这个方法,进入看。

public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
   return doGetBean(name, requiredType, null, false);
}

接着走到doGetBean()

protected <T> T doGetBean(
      String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
      throws BeansException {
   String beanName = transformedBeanName(name);
   Object bean;
   Object sharedInstance = getSingleton(beanName); //根据name去单例对象池拿出对象
   if (sharedInstance != null && args == null) {
      if (logger.isTraceEnabled()) {
         if (isSingletonCurrentlyInCreation(beanName)) {
            logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
                  "' that is not fully initialized yet - a consequence of a circular reference");
         }
         else {
            logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
         }
      }
      bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
   }
   /**无关很多,略。包括忽略了对象类型检测的部分,会在下面单独说**/
   return (T) bean;
}

最终这个方法返回的还是bean,也就是方法最前面创建的变量。其实到这里已经很明显了,通过sharedInstance = getSingleton(beanName)从单例对象池里取出已经实例化好的对象,然后到bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);赋值,最终返回出去,一层层往上返回直到field.set(target, getResourceToInject(target, requestingBeanName));给字段赋值。

单例对象池没有对象怎么办

到这里肯定有同学会有疑问:如果单例对象池找不到对应的实例对象怎么办?其实这个问题基本上不会遇到,因为Spring容器在初始化的时候会在@ComponentScan的时候把所有注册到容器里的类实例化出来,只要变量名符合Spring的命名规则就一定可以找到。

异常is expected to be of type ‘xxx’ but was actually of type ‘xxx’

【源码解析异常is expected to be of type ‘xxx’ but was actually of type ‘xxx’ 是如何发生的】

异常expected single matching bean but found 2

【源码解析异常expected single matching bean but found 2是如何发生的】
这篇文章里有分析,@Resource是如何进行byType注入的。

总结

通过上面的源码流程以及报错源码流程的分析,可以得出这样的一个结论:无论是@Autowired或者@Resource都不能单纯的说是byType或者byName去自动注入的。二者其实是交叉使用,作为双保险。只能说@Autowired默认byType进行的注入,而@Resource默认是byName进行的注入。但是依然不可避免的出现异常,因此也可以说规范命名十分的重要。由于篇幅的原因,其实还有两个异常问题没有说,那就是is expected to be of type 'xxx' but was actually of type 'xxx'expected single matching bean but found 2产生的原因,笔者会单独作为两个篇章进行讲解。这两篇文章已经作为本文的两个标题补充到博客里。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
回答: 当使用Spring的@Resource注解时,如果没有找到匹配的bean,会抛出异常。这可能是因为没有在Spring上下文中找到与@Resource注解中指定的name或type属性匹配的bean。\[1\] @Resource注解是JSR-250提供的Java标准注解,大多数框架都支持它。与之相比,@Autowired注解是Spring框架特有的注解,如果切换到其他框架,@Autowired注解可能会失效。\[2\] 在使用@Resource注解时,可以通过指定name属性或type属性来精确匹配要注入的bean。如果没有指定name属性,则会根据type属性来查找唯一匹配的bean进行注入。如果找不到匹配的bean或找到多个匹配的bean,则会抛出异常。\[3\] #### 引用[.reference_title] - *1* [Spring @Resource注解](https://blog.csdn.net/qq_36761831/article/details/90294875)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Spring中@Autowired和@Resource的区别](https://blog.csdn.net/Weixiaohuai/article/details/120853683)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [一文弄懂Spring源码之@Resource注解](https://blog.csdn.net/q1472750149/article/details/122213803)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值