一个属性同时使用Autowired和Resource注解会发生什么?
首发于公众号:BiggerBoy
右侧图片wx扫码关注有惊喜
欢迎关注,查看更多技术文章
如题,如果在同一个属性上使用@Autowired注解注入bean1,然后使用@Resource注解注入bean2会发生什么?
先给出几个猜想:
1.报错,不能重复注入。
2.先注入bean1再注入bean2,类似于map中put同一个key覆盖value。
3.注入bean1。Spring注入前判断属性注入过不再重复注入,且先处理@Autowired
4.注入bean2。Spring注入前判断属性注入过不再重复注入,且先处理@Resource
测试验证
首先定义一个OrderService,beanName为orderService,desc属性默认是default
@Component("orderService")
public class OrderService {
private String desc = "default";
//getter setter toString
}
然后使用@Bean的方式再注册一个OrderService,beanName为orderService1,desc属性赋值为update
@Bean("orderService1")
public OrderService orderService1() {
OrderService orderService = new OrderService();
orderService.setDesc("update");
return orderService;
}
然后我们在UserService的orderService属性上同时加上@Autowired注解(会注入beanName为orderService的实例),和@Resource(name = "orderService1")注解,即指定注入name为orderService1的bean。
@Component
public class UserService {
@Autowired
@Resource(name = "orderService1")
private OrderService orderService; public void test() {
System.out.println("test:"+orderService);
}
}
然后我们在测试类中,从Spring容器中获取UserService,最终UserService持有的是哪个bean?
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
}
控制台输出:
test:OrderService{desc='update'}
可见,UserService中 OrderService属性注入的是orderService1。调整属性上注解的顺序,控制台输出依旧如此,说明和注解顺序没有关系。这和我们的猜想2相同,即Spring先处理@Autowired注解注入orderService,再处理@Resource注解注入orderService1,从而覆盖了先注入的。我们这个结论对不对呢?往下看,从源码中找答案比较靠谱。
注入流程
在看源码之前,先看一下Spring注入流程。在Spring框架中,Bean的属性注入可以使用@Autowired注解也可以使用@Resource注解。
Autowired
@Autowired注解是Spring框架提供的,可以写在:
属性上:先根据属性类型去找Bean,如果找到多个再根据属性名确定一个。
构造方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个。
set方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个。
总体就是@Autowired先基于类型去找Bean,如果找到多个Bean,再根据name确定一个。
Resource
@Resource注解是Java提供的,可以用在方法上、属性上,它的注入流程是:
1、先判断BeanFactory中是否存在注入点名字(属性名字或方法参数名字)对应的Bean,
2、如果存在则只会根据注入点名字(属性名字或方法参数名字)去找bean,如果找不到对应的bean会报错。
3、如果不存在,再去判断@Resource注解中是否指定了name属性,
4、如果指定了,则只会根据name去找bean,如果找不到对应的bean会报错。
5、如果没有指定,则会和@Autowired注解一样,先byType再byName。
依赖注入源码分析
在Bean创建过程中,会对Bean的属性进行赋值,即依赖注入,Spring是怎么实现的呢?
我们都知道spring在创建bean的过程中有很多的扩展点,说白了就是留了很多接口,在创建bean的过程调用这些接口的方法,通过实现这些接口嵌入自定义的处理逻辑,从而完成很多功能。Spring的依赖注入也不例外。
先剧透一下,Spring中CommonAnnotationBeanPostProcessor
、AutowiredAnnotationBeanPostProcessor
实现了InstantiationAwareBeanPostProcessor
的postProcessProperties
方法,完成@Resource、@Autowired的依赖注入。
如下两张图为CommonAnnotationBeanPostProcessor
、AutowiredAnnotationBeanPostProcessor
的局部类图。
具体流程如下:
1、属性填充。
Spring实例化Bean后,调用populateBean方法对Bean进行属性填充。
在AbstractAutowireCapableBeanFactory
的doCreateBean
方法中创建Bean实例,之后,调用同一个类中的populateBean
方法做属性填充,即处理@Autowired注解和@Resource注解。
关键代码如下:
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
// 这里会调用AutowiredAnnotationBeanPostProcessor的postProcessProperties()方法,会直接给对象中的属性赋值
// AutowiredAnnotationBeanPostProcessor内部并不会处理pvs,直接返回了
PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
if (filteredPds == null) {
filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
return;
}
}
pvs = pvsToUse;
}
2、属性注入。
调用InstantiationAwareBeanPostProcessor
的postProcessProperties
方法完成属性注入。
上述代码通过getBeanPostProcessorCache().instantiationAware
获取所有InstantiationAwareBeanPostProcessor
的实现类,循环并调用其postProcessProperties
方法。
通过debug我们会发现,getBeanPostProcessorCache().instantiationAware
获取到到的实现类CommonAnnotationBeanPostProcessor
在实现类AutowiredAnnotationBeanPostProcessor
的前面,如下图所示:
这就意味着Spring先处理@Resource后处理@Autowired,说明我们前面的猜想是错误的。所以,Spring是先处理@Resource的对属性注入,再处理@Autowired,发现属性已经被注入了,就不再重复注入?是不是这样我们继续看源码。CommonAnnotationBeanPostProcessor
和AutowiredAnnotationBeanPostProcessor
的postProcessProperties
方法非常相似,AutowiredAnnotationBeanPostProcessor
的postProcessProperties
方法源码如下,只比CommonAnnotationBeanPostProcessor
的多了一个BeanCreationException
的catch。
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
// 找注入点(所有被@Autowired注解了的Field或Method)
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
//注入
metadata.inject(bean, beanName, pvs);
}catch (BeanCreationException ex) {
throw ex;
}catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
return pvs;
}
findAutowiringMetadata
的逻辑是从injectionMetadataCache
(map,缓存注入点元数据)中获取InjectionMetadata
,这里从缓存map中能够获取到InjectionMetadata
,然后调用其inject方法完成属性注入。CommonAnnotationBeanPostProcessor
的findAutowiringMetadata
逻辑也是如此。
我们看下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);
}
}
}
该方法遍历注入点集合elementsToIterate
,调用inject方法完成属性注入,只不过elementsToIterate
的取值有点小逻辑,如果checkedElements
不为null就取checkedElements
,否则取injectedElements
。
对于刚刚我们的猜想,会不会是elementsToIterate
没有元素,所以AutowiredAnnotationBeanPostProcessor
属性注入直接跳过了,所以注入的是@Resource注解的?而且,我们还没看到checkedElements
和injectedElements
是在哪赋值的。
3、寻找注入点。
通过源码可以看到,实例化Bean之后,调用populateBean
方法属性填充之前调了一个本类的方法applyMergedBeanDefinitionPostProcessors
protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) {
processor.postProcessMergedBeanDefinition(mbd, beanType, beanName);
}
}
CommonAnnotationBeanPostProcessor
和AutowiredAnnotationBeanPostProcessor
也实现了相应的接口
来看看它们的实现
//CommonAnnotationBeanPostProcessor
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null);
metadata.checkConfigMembers(beanDefinition);
}
//AutowiredAnnotationBeanPostProcessor
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
metadata.checkConfigMembers(beanDefinition);
}
它们都调用了findAutowiringMetadata
方法寻找注入点,包装为InjectionMetadata
对象并将注入点集合赋值给injectedElements
属性,然后将InjectionMetadata
对象放到缓存map中,所以在postProcessProperties
中调用findAutowiringMetadata
可以从缓存map中获取到InjectionMetadata
。
4、重复的注入点不会重复注入。
重复的注入点不会添加到checkedElements
中。
还都调用了checkConfigMembers
方法,上一个方法findAutowiringMetadata
完成了injectedElements
属性的赋值,那么这个方法是对checkedElements
方法进行赋值的。
public void checkConfigMembers(RootBeanDefinition beanDefinition) {
Set<InjectedElement> checkedElements = new LinkedHashSet<>(this.injectedElements.size());
for (InjectedElement element : this.injectedElements) {
Member member = element.getMember();
// 将Field或Method记录到BeanDefinition中的externallyManagedConfigMembers中,表示该Field或Method是BeanFactory外部管理的
if (!beanDefinition.isExternallyManagedConfigMember(member)) {
beanDefinition.registerExternallyManagedConfigMember(member);
checkedElements.add(element);
}
}
this.checkedElements = checkedElements;
}
这个方法的大致逻辑是将injectedElements
的元素,放到checkedElements中, 只有满足条件的才放到checkedElements
中,判断条件是不在RootBeanDefinition#externallyManagedConfigMembers
集合中,把元素添加到该集合和checkedElements
中。
由于getBeanPostProcessorCache().mergedDefinition
获取到的实现类,CommonAnnotationBeanPostProcessor
在AutowiredAnnotationBeanPostProcessor
前面。
所以对于同一个属性,第二次判断时
!beanDefinition.isExternallyManagedConfigMember(member)
条件不成立,元素就不会放到checkedElements
中
故AutowiredAnnotationBeanPostProcessor
在执行postProcessProperties
方法时,调用InjectionMetadata
的inject
方法,此时checkedElements
不会有该注入点,就不会重复注入。
结论
通过源码了解到,并不是单纯的处理顺序的先后被覆盖的原因,依赖注入是创建Bean实例后完成的,通过实现MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition
寻找注入点放入缓存中,再通过实现InstantiationAwareBeanPostProcessor#postProcessProperties
从缓存中获取注入点,完成属性注入。其中重复的注入点不会重复放到缓存中,所以先执行的CommonAnnotationBeanPostProcessor
会完成属性注入,后执行的AutowiredAnnotationBeanPostProcessor
自然不会再注入。
便于理解流程,画了一张图:
动动你发财的小手,点点关注
往期文章推荐
一个属性同时使用Autowired和Resource注解会发生什么?
一个属性同时使用Autowired和Resource注解会发生什么?的更多相关文章
- @Autowired和@Resource注解的一个意外重要区别
今天上午,因为公司要跟客户展示最近开发的项目,然后安排了我重新构建一个template项目,用来向客户展示参考.基于已开发好的代码,我在进行一些简化抽取的时候出现了一个有趣的问题 因为我们有一个spr ...
- @Autowired 和 @Resource注解, 一个接口有多个实现类的时候Spring注入遇到的问题
先说下我遇到的问题,有一个接口 CompensationService, 有两个实现类 MusicCompensationStrategyImpl 和 TakeDeliveryCompensati ...
- Spring下的@Inject、@Autowired、@Resource注解区别(转)
1.@Inject javax.inject JSR330 (Dependency Injection for Java) 这是JSR330中的规范,通过AutowiredAnnotationBean ...
- Java框架spring 学习笔记(九):Spring的bean管理(@Required、@Component、@Autowired、@Resource注解)
注解:代码里面特殊的标记,使用注解可以完成相关功能 注解写法:@注解名称(属性名.属性值) @Required 用在set方法上,一旦用了这个注解,那么容器在初始化bean的时候必须要进行set,也就 ...
- @Autowired和@Resource注解的区别
@Autowired注解是按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false.如果我们想使用按名称装配,可以结合@Qualifier注 ...
- 解决非controller使用,@Autowired或者@Resource注解注入Mapper接口为null的问题
知识点:在service层中注入其它的service接口或者mapper接口都是可以的 但是在封装的Utils工具类中或者非controller普通类中使用@Autowired@Resource注解注 ...
- 关于@Autowired和@Resource注解区别
区分一下@Autowired和@Resource两个注解的区别: 1.@Autowired默认按照byType方式进行bean匹配,@Resource默认按照byName方式进行bean匹配 2.@A ...
- (转)用@Resource注解完成属性装配
http://blog.csdn.net/yerenyuan_pku/article/details/52858878 前面我们讲过spring的依赖注入有两种方式: 使用构造器注入. 使用属性set ...
- Spring AOP注解通过@Autowired,@Resource,@Qualifier,@PostConstruct,@PreDestroy注入属性的配置文件详解
本文介绍了使用Spring注解注入属性的方法.使用注解以前,注入属性通过类以及配置文件来实现.现在,注入属性可以通过引入@Autowired注解,或者@Resource,@Qualifier,@Pos ...
- Spring AOP注解通过@Autowired,@Resource,@Qualifier,@PostConstruct,@PreDestroy注入属性的
本文介绍了使用spring注解注入属性的方法. 使用注解以前,注入属性通过类以及配置文件来实现.现在,注入属性可以通过引入@Autowired注解,或者@Resource,@Qualifier,@Po ...
随机推荐
- springboot整合security实现权限控制
1.建表,五张表,如下:1.1.用户表CREATE TABLE `t_sys_user` ( `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT ...
- 了解GraphRAG
了解GraphRAG 转载:从零实现大模型-GraphRAG,构建LLM中的关系数据库 开源地址:https://github.com/microsoft/graphrag 论文:From Local ...
- Transformer 原理图解
转载:小白看得懂的 Transformer (图解) 引言 谷歌推出的BERT模型在11项NLP任务中夺得SOTA结果,引爆了整个NLP界.而BERT取得成功的一个关键因素是Transformer的强 ...
- 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
在本地部署DeepSeek大模型实现联网增强的AI应用 一.前言 在本地部署大语言模型(LLM)并赋予其联网能力,是当前AI应用开发的重要方向.本文将基于Microsoft Semantic Kern ...
- 独立开发经验谈:如何通过 Docker 让潜在客户快速体验你的系统
我在业余时间开发了一款自己的独立产品:升讯威在线客服与营销系统.陆陆续续开发了几年,从一开始的偶有用户尝试,到如今线上环境和私有化部署均有了越来越多的稳定用户,在这个过程中,我也积累了不少如何开发运营 ...
- vue-element-template实现顶部菜单栏
一.框架侧边栏改为顶部导航栏 1.复制src/layout/componets/Sidebar所有文件至同级目录,改名为Headbar 2.src/layout/components/index.js ...
- DeepSeek模型量化
技术背景 大语言模型(Large Language Model,LLM),可以通过量化(Quantization)操作来节约内存/显存的使用,并且降低了通讯开销,进而达到加速模型推理的效果.常见的就是 ...
- 安卓编译报错Execution failed for task ‘:expo-modules-core:prepareBoost‘. Not in GZIP format的解决方案
作者: Kovli 重要通知:红宝书第5版2024年12月1日出炉了,感兴趣的可以去看看,https://u.jd.com/saQw1vP 红宝书第五版中文版 红宝书第五版英文原版pdf下载(访问密码 ...
- DevExpress MVVM Framework. Interaction of ViewModels. Messenger
学习记录: 学习地址:https://community.devexpress.com/blogs/wpf/archive/2013/12/13/devexpress-mvvm-framework-i ...
- 从“技术宅”到"机器人教父",那个用机器人改变世界的年轻人
写在前面 随着民营企业座谈会的召开,有一位年轻的企业家王兴兴映入了我们的视野.没错就是那个让机器人从实验室走向舞台中央的年轻人. 大家对今年春晚的机器人扭秧歌应该都还印象深刻吧,它就出自于王兴兴创办的 ...