Spring源码三千问从源码分析@Resource与@Autowired的区别
Posted 老王学源码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 注入规则的源码分析
我们可以找到如下的代码:
看 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 自动注入的规则是什么?
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() 上打个断点,跟进去看一下。
果不其然,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():
@Autowired 的装配顺序:
- 按 type 在上下文中查找匹配的 bean
- 如果有多个bean,则按 name 进行匹配
2.1 如果有 @Qualifier 注解,则按照 @Qualifier 指定的name进行匹配
2.2 如果没有,则按照 field 的 name 进行匹配 - 匹配不到,则报错。
如果设置 @Autowired(required=false),则注入失败时不会抛出异常
总结
-
@Resource
@Resource 在不指定 name 的情况下,默认是按 field 的 name(默认name)来注入的;如果默认的 name 找不到 bean 的话,就会按 type 注入。 - @Autowired
@Autowired 默认按 type 进行注入,当匹配到多个 bean 时,再按照 name 来进行注入。
如果本文对你有所帮助,欢迎点赞收藏!
有关 Spring 源码方面的问题欢迎一起交流,备注:51cto (vx: Kevin-Wang001)
以上是关于Spring源码三千问从源码分析@Resource与@Autowired的区别的主要内容,如果未能解决你的问题,请参考以下文章
#yyds干货盘点#Spring源码三千问@Lazy延迟加载与延迟注入有什么区别?
#yyds干货盘点# Spring源码三千问Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?
#yyds干货盘点# Spring源码三千问AdviceAdvisorAdvised都是什么接口?
#yyds干货盘点# Spring 源码三千问同样是AOP代理bean,为什么@Async标记的bean循环依赖时会报错?
#yyds干货盘点# Spring源码三千问为什么要用三级缓存来解决循环依赖问题?二级缓存行不行?一级缓存行不行?
#yyds干货盘点# Spring源码三千问BeanDefinition详解——什么是 RootBeanDefinition?merged bean definition 又是什么鬼?