Spring源码三千问从源码分析@Resource与@Autowired的区别

亦余心之所善兮,虽九死其犹未悔。这篇文章主要讲述Spring源码三千问从源码分析@Resource与@Autowired的区别相关的知识,希望能为你提供帮助。
@TOC
前言从我们接触 Spring 起,就开始使用它的自动注入功能。最常用的注解有: @Autowired、@Resource
我们经常听说 @Autowired 优先按类型注入,其次再按 name 注入。而 @Resource 则正好相反,它优先按 name 注入,其次再按 type 注入。
真的是这样吗?怎么从源码的角度来进行解释?
版本约定Spring 5.3.9 (通过 SpringBoot 2.5.3 间接引入的依赖)
正文下面,我们就从源码的视角来观察 @Autowired、@Resource 在依赖注入时的区别
准备工作首先,我们准备一个干净的工程。
我们只需要一段如下的代码来进行研究:

@SpringBootApplication public class Application { @Resource UserService userService; public static void main(String[] args) { SpringApplication app = new SpringApplication(com.kvn.Application.class); app.setBannerMode(Banner.Mode.OFF); app.run(args); } }

正式开始 @Resource 注入规则的源码分析
我们可以找到如下的代码:
Spring源码三千问从源码分析@Resource与@Autowired的区别

文章图片

看 InjectionMetadata 的命名,应该是和自动注入有关的。
它里面有个 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); } } }

到这里,一切都只是我们的猜测。我们可以看一下 InjectedElement#inject() 里面究竟干了啥?
我们可以看到,InjectedElement 有几个子类,分别来处理不同注解的注入方式:
Spring源码三千问从源码分析@Resource与@Autowired的区别

文章图片

看到这里,我们是不是有点小兴奋了,但是,我们还是不能忘记我们的初衷:@Resource 自动注入的规则是什么?
ResourceElement 类比较简单,包含一个构造方法 和 一个 getResourceToInject() 方法:
private class ResourceElement extends LookupElement {private final boolean lazyLookup; 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(); this.isDefaultName = !StringUtils.hasLength(resourceName); if (this.isDefaultName) { // 不指定 name 的情况下,默认取 filed 的 name 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) { // @Resource 中指定的 name 属性可以通过 占位符 来指定 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 = https://www.songbingjia.com/android/resource.lookup(); this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName()); // 对 @Lazy 的支持 Lazy lazy = ae.getAnnotation(Lazy.class); this.lazyLookup = (lazy != null & & lazy.value()); }@Override protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) { // 对 @Lazy 的支持 return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) : getResource(this, requestingBeanName)); } }

构造方法里面将 @Resource 注解中的数据进行了解析。另外的 getResourceToInject() 好像是跟自动注入有关系的,但是也不太确定。
这时,断点+调试大法就上线了。我们可以在 getResourceToInject() 上打个断点,跟进去看一下。
果不其然,getResourceToInject() 最终会调用到 CommonAnnotationBeanPostProcessor#autowireResource():
protected Object autowireResource(BeanFactory factory, LookupElement element, String requestingBeanName) { Object resource; Set< String> autowiredBeanNames; String name = element.name; if (factory instanceof AutowireCapableBeanFactory) { AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory; DependencyDescriptor descriptor = element.getDependencyDescriptor(); // @Resoure 没有指定 name(name 是默认值),并且容器中不存在这个默认 name 对应的 bean,那么就使用 byType 方式进行注入 if (this.fallbackToDefaultTypeMatch & & element.isDefaultName & & !factory.containsBean(name)) { autowiredBeanNames = new LinkedHashSet< > (); // 按 byType 方式解析依赖进行注入 resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null); if (resource == null) { throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object"); } } else { // 满足如下条件,则按 byName 的方式解析依赖进行注入: // 1. 如果 @Resource 指定了 name,则按指定 name 解析依赖进行注入 // 2. 如果容器中存在 name 对应的 bean,则按 byName 方式进行注入 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; }

看到上面的源码后,我们疑惑基本上也就解决了: @Resource 默认是按 name 注入的,其次是按 type。
其实,准确的表达应该是:
@Resource 在不指定 name 的情况下,默认是按 field 的 name 来注入的;如果按默认的 name 找不到 bean,则会按 type 注入。
@Autowired 注入规则的源码分析
@Autowired 的分析与上面 @Resource 的分析基本上一样。所以这里就直接上结论了:
@Autowired 属性注入的处理是在 AutowiredFieldElement#inject() 方法中。
最终会调用到下面的方法,DefaultListableBeanFactory#doResolveDependency():
Spring源码三千问从源码分析@Resource与@Autowired的区别

文章图片

@Autowired 的装配顺序:
  1. 按 type 在上下文中查找匹配的 bean
  2. 如果有多个bean,则按 name 进行匹配
    2.1 如果有 @Qualifier 注解,则按照 @Qualifier 指定的name进行匹配
    2.2 如果没有,则按照 field 的 name 进行匹配
  3. 匹配不到,则报错。
    如果设置 @Autowired(required=false),则注入失败时不会抛出异常
总结
  • @Resource
    @Resource 在不指定 name 的情况下,默认是按 field 的 name(默认name)来注入的;如果默认的 name 找不到 bean 的话,就会按 type 注入。
  • @Autowired
    @Autowired 默认按 type 进行注入,当匹配到多个 bean 时,再按照 name 来进行注入。
如果本文对你有所帮助,欢迎点赞收藏!
【Spring源码三千问从源码分析@Resource与@Autowired的区别】有关 Spring 源码方面的问题欢迎一起交流,备注:51cto (vx: Kevin-Wang001)

    推荐阅读