@Resource与@Autowired的区别

@Resource与@Autowired的区别

前言

从我们接触 Spring 起,就开始使用它的自动注入功能。最常用的注解有: @Autowired、@Resource
我们经常听说 @Autowired 优先按类型注入,其次再按 name 注入。而 @Resource 则正好相反,它优先按 name 注入,其次再按 type 注入。
真的是这样吗?怎么从源码的角度来进行解释?

javax.annotation.Resource
org.springframework.beans.factory.annotation.Autowired

版本约定

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?
答:@Resource 是 java 原生的注解,在 Spring 源码中使用较少,很容易通过 IDEA 快速检索到哪些类中使用到了这个注解。
从而通过猜测+排除的方法,快速找到 Spring 源码中对 @Resource 注入的处理代码。

正式开始

@Resource 注入规则的源码分析

我们可以找到如下的代码:
@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 有几个子类,分别来处理不同注解的注入方式:
@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 = 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() 上打个断点,跟进去看一下。

看 @Resource 的源码,我们发现了两个小东西:
1. @Resource 中指定的 name 属性可以通过 占位符 来指定
2. 找到了 Spring 对 @Lazy 延迟注入的一个小线索
这两个发现跟我们要研究的特性没有太大的关联,在看主逻辑时不需要分心去看。
但是做为一个有技术敏感性的程序猿,细心的你可以将它记录下来,等到有时间了,也可以拿出来研究一下。_

果不其然,getResourceToInject() 最终会调用到 CommonAnnotationBeanPostProcessor#autowireResource():

protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName) {
    Object resource;
    Set<String> autowiredBeanNames;
    String name = element.name;

    if (factory instanceof AutowireCapableBeanFactory) {
        AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
        DependencyDescriptor descriptor = element.getDependencyDescriptor();
        // 如果容器中不存在 name 这个 bean,那么就使用 type 进行注入
        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 {
            // 容器中存在 name,则按 name 进行注入
            // 如果是指定了 name 的,则只能按 name 进行注入(上面的 if 条件决定的)
            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 来注入的,其次是按 type 注入。

@Autowired 注入规则的源码分析

@Autowired 的分析与上面 @Resource 的分析基本上一样。所以这里就直接上结论了:
@Autowired 属性注入的处理是在 AutowiredFieldElement#inject() 方法中。
最终会调用到下面的方法,DefaultListableBeanFactory#doResolveDependency():
@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 来注入的,其次是按 type 注入。

  • @Autowired
    @Autowired 默认按 type 进行注入,当匹配到多个 bean 时,再按照 name 来进行注入。

如果本文对你有所帮助,欢迎点赞收藏!

上一篇:Flume - Kafka日志平台整合


下一篇:AUTOSAR-ExclusiveArea