亦余心之所善兮,虽九死其犹未悔。这篇文章主要讲述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 = 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():
文章图片
@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源码三千问从源码分析@Resource与@Autowired的区别】有关 Spring 源码方面的问题欢迎一起交流,备注:51cto (vx: Kevin-Wang001)
推荐阅读
- Paper2018_多机器人领航-跟随型编队控制
- OpenCV通道的分离和合并
- #yyds干货盘点#Java ASM系列((091)冗余变量分析)
- 阅 物理模块浅析[原理分析] 自我摘录
- #yyds干货盘点#学不懂Netty(看不懂源码?不存在的,这篇文章手把手带你阅读Netty源码)
- docker资源隔离与资源限制
- #yyds干货盘点# Netty源码分析之Reactor线程模型详解
- docker镜像文件分层
- flutter 中的列表的性能优化续集#yyds干货盘点#