背景

我们常听别人说:“Spring中的@Autowired是按类型来依赖注入的,@Resource是按名称来依赖注入的”,那么这句话到底正不正确呢?

这里我先下个定论,“Spring中的@Autowired是按类型来依赖注入的,@Resource是按名称来依赖注入的”这句话正确也不正确。要怎么理解呢?@Resource注解是优先按照名称来进行依赖注入,但如果按名称找不到对应的Bean时,还是按类型来进行依赖注入;同样,当某个接口存在多个实现类,并且这些实现类都交给IoC容器管理,那么@Autowired也会尝试根据名称来进行依赖筛选。

Talk is cheap. Show me the code

第一步:首先定义一个接口:UserService以及实现类UserServiceImpl。

package com.xxx.hyl.dependency.inject;public interface UserService {
}package com.xxx.hyl.dependency.inject.impl;import com.xxx.hyl.dependency.inject.UserService;
import org.springframework.stereotype.Service;import javax.annotation.Resource;/*** IoC容器默认beanName生成规则 是将类名首字母小写,这里我们手动指定beanName,来测试{@link Resource}注解是否真的只是按名称进行依赖注入** @author 君战 **/
@Service("customizedBeanName")
public class UserServiceImpl implements UserService {}

第二步:编写一个启动类,使用@Resource注解来注入UserService 实现类,启动main方法。

package com.xxx.hyl.dependency.inject;import com.xxx.hyl.dependency.inject.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;import javax.annotation.Resource;/****  演示{@link Autowired} 是否真的只是按照类型来进行依赖注入*  演示{@link Resource}  是否真的只是按照名称来进行依赖注入**  @author 君战* **/
public class DependencyInjectionDemo {// 随便指定beanName@Resourceprivate UserService bean_111;public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();context.register(DependencyInjectionDemo.class, UserServiceImpl.class);context.refresh();// 如果@Resource注解真的只是按照名称来进行依赖注入,那么下面这段代码将会抛出空指针异常,因为IoC容器中没有一个名字叫做“bean_111”的BeanSystem.out.println(context.getBean(DependencyInjectionDemo.class).bean_111.hashCode());}}

第三步:查看控制台。

1629911510Process finished with exit code 0

可以发现,我们将属性名随便修改,使用@Resource注解依然可以注入。那么我们接下来再尝试下@Autowired注解是否真的只是按照类型来进行注入的。这里我们修改下代码,再增加一个UserService实现类。

package com.xxx.hyl.dependency.inject.impl;import com.xxx.hyl.dependency.inject.UserService;
import org.springframework.stereotype.Service;import javax.annotation.Resource;/*** IoC容器默认beanName生成规则 是将类名首字母小写,这里我们手动指定beanName,来测试{@link Resource}注解是否真的只是按名称进行依赖注入** @author 君战 **/
@Service("customizedBeanName2")
public class UserServiceImpl2 implements UserService {}

修改启动类代码,执行main方法。

package com.xxx.hyl.dependency.inject;import com.xxx.hyl.dependency.inject.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;import javax.annotation.Resource;/*** * 演示{@link Autowired} 是否真的只是按照类型来进行依赖注入 *   演示{@link Resource} 是否真的只是按照名称来进行依赖注入*   @author 君战* <p>**/
public class DependencyInjectionDemo {// 随便指定beanName@Resource private UserService bean_111;// 这里我们将属性名指定为 UserServiceImpl2 @Service("customizedBeanName2") 指定的beanName@Autowired private UserService customizedBeanName2;public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();context.register(DependencyInjectionDemo.class, UserServiceImpl.class);context.refresh();// 如果@Resource注解真的只是按照名称来进行依赖注入,那么程序将启动失败,// 因为IoC容器中没有一个名字叫做“bean_111”的BeanSystem.out.println("@Resource注解注入的结果:"+ context.getBean(DependencyInjectionDemo.class).bean_111.hashCode());// 如果@Autowired注解真的只是按照类型来进行依赖注入的,因为UserService存在多个实现类,那么// 程序将启动失败,@Autowired注解的required属性默认为true,即必须注入。System.out.println("@Autowired注解注入的结果:"+context.getBean(DependencyInjectionDemo.class).customizedBeanName2.hashCode());}
}

查看控制台。

@Resource注解注入的结果:1250391581
@Autowired注解注入的结果:1250391581Process finished with exit code 0

通过以上代码测试,我们可以得出一个结论,@Resource并不真的只是按照名称来进行依赖注入,@Autowired也并不是真的只是按照类型来进行依赖注入。

底层原理分析

首先来分析下对于添加了@Resource注解的字段是如何处理的,处理逻辑在CommonAnnotationBeanPostProcessor的autowireResource方法中。

// CommonAnnotationBeanPostProcessor#autowireResource
protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)throws NoSuchBeanDefinitionException {Object resource;Set<String> autowiredBeanNames;String name = element.name;// 判断BeanFactory是否是AutowireCapableBeanFactory,DefaultListableBeanFactory实现了该接口,判断成立。if (factory instanceof AutowireCapableBeanFactory) {AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;DependencyDescriptor descriptor = element.getDependencyDescriptor();// 重点是这里!!! 判断IoC容器中是否包含指定name的Bean(!factory.containsBean(name)),if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {autowiredBeanNames = new LinkedHashSet<>(); // 执行到这里意味着根据name(可能是字段名,也可能是方法参数名)并未找到相应的Bean,// 调用IoC容器的resolveDependency方法,该方法是根据类型去查找Bean。// 对@Autowired注解的处理也是调用该方法resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);if (resource == null) {throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");}} else {// 执行这里就意味着根据name(可能是字段名,也可能是方法参数名)// 找到了对应的Bean,因此根据name来获取相关Beanresource = 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;
}

接下来我们再分析下当再字段上添加@Autowired注解的处理逻辑-AutowiredFieldElement的inject方法(由此可以推导出来当在方法上添加@Autowired注解上,使用的就是AutowiredMethodElement)。

// AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Field field = (Field) this.member;Object value;if (this.cached) { // 如果相关字段已经被处理过,直接走缓存。value = resolvedCachedArgument(beanName, this.cachedFieldValue);} else {DependencyDescriptor desc = new DependencyDescriptor(field, this.required);desc.setContainingClass(bean.getClass());Set<String> autowiredBeanNames = new LinkedHashSet<>(1);Assert.state(beanFactory != null, "No BeanFactory available");TypeConverter typeConverter = beanFactory.getTypeConverter();try { // 对@Autowired注解的处理重点是这里调用AutowireCapableBeanFactory的resolveDependency方法。// 前面我们在分析@Resource注解处理逻辑时,已经提过,当未根据name找到对应的Bean时,// 也是调用AutowireCapableBeanFactory的resolveDependency方法。value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);}catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);}// 删除与本次分析无关代码.....if (value != null) {ReflectionUtils.makeAccessible(field);field.set(bean, value);}}
}

这里还有一个问题就是,@Autowired注解也会根据名称来过滤依赖吗?

答案是肯定的,根据名称来进行依赖过滤的代码在doResolveDependency方法中,

public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);try { // 判断该依赖描述符之前是不是已经解析过Object shortcut = descriptor.resolveShortcut(this);if (shortcut != null) {return shortcut;}Class<?> type = descriptor.getDependencyType();// 这里是解析@Value注解的地方,不熟悉的小伙伴可以查看我的另一篇博文-// 《Spring-外部配置的值是如何通过@Value注解获取的?》// 地址:https://blog.csdn.net/m0_43448868/article/details/111590614Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);if (value != null) {if (value instanceof String) {String strVal = resolveEmbeddedValue((String) value);BeanDefinition bd = (beanName != null && containsBean(beanName) ?getMergedBeanDefinition(beanName) : null);value = evaluateBeanDefinitionString(strVal, bd);}TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());try {return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());}catch (UnsupportedOperationException ex) {return (descriptor.getField() != null ?converter.convertIfNecessary(value, type, descriptor.getField()) :converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));}}// 这里是解析多类型Bean的地方,如何注入某个类型的多个实现类,// 可以查看我的另一篇博文-《如何使用@Autowire注入某个类型的多个实现类?》// 地址:https://blog.csdn.net/m0_43448868/article/details/111588584Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);if (multipleBeans != null) {return multipleBeans;}// 通常我们注入的都是某个类型的单实现类,执行findAutowireCandidates方法。// 延伸一点,依赖注入和依赖查找数据来源最重要的区别也是在这里完成的,// 详细介绍可以查看我的另一篇博文-《在Spring IoC中,依赖注入和依赖查找的数据来源一样吗?》// 地址:https://blog.csdn.net/m0_43448868/article/details/111866510Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);if (matchingBeans.isEmpty()) { // 如果根据某个类型获取到匹配依赖为空if (isRequired(descriptor)) { // 判断该依赖是否是必须的,例如@Autowired的required属性(默认为true)raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);// 抛出异常}return null;}String autowiredBeanName;Object instanceCandidate;// 如果匹配到的依赖有多个,执行determineAutowireCandidate方法来决定到底使用哪一个。if (matchingBeans.size() > 1) {autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);if (autowiredBeanName == null) {if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);} else {return null;}}instanceCandidate = matchingBeans.get(autowiredBeanName);}else {// We have exactly one match.Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();autowiredBeanName = entry.getKey();instanceCandidate = entry.getValue();}// 删除与本次分析无关代码......return result;}finally {ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);}
}
// DefaultListableBeanFactory#determineAutowireCandidate
protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {Class<?> requiredType = descriptor.getDependencyType();// 优先解析@Primary注解,看到这里应该就明白当某个类型存在多个实现类,// 并且多个实现类都交由IoC容器管理,为什么可以通过@Primary注解来控制使用哪一个Bean。String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);if (primaryCandidate != null) {return primaryCandidate;}// 这里就是解析 javax中的javax.annotation.Priority注解,如果某个类型存在多个实现类,// 并且多个实现类都交由IoC容器管理,但是并不想使用@Primary注解来决定哪一个是主要的,// 那么可以使用@Priority注解。底层IoC容器也是支持的。String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);if (priorityCandidate != null) {return priorityCandidate;}// 最后,就是根据名字来进行匹配。// 例如前面我们在演示代码DependencyInjectionDemo类中  @Autowired private UserService customizedBeanName2;// 这里在使用@Autowired注解声明依赖UserService 时,字段名为customizedBeanName2。// 这样当IoC容器执行到这里时,就可以拿着beanName和字段名进行一一比对,当两者匹配时,直接return该依赖。for (Map.Entry<String, Object> entry : candidates.entrySet()) {String candidateName = entry.getKey();Object beanInstance = entry.getValue();if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||matchesBeanName(candidateName, descriptor.getDependencyName())) {return candidateName;}}return null;
}

总结

@Resource注解并不是仅仅只是按照名称来进行注入,当根据名称未找到对应bean时,也是按照类型来进行注入。而@Autowired也不仅仅只是按照来行来进行注入,例如当某个类型的Bean有多个时,也会按照名称来进行依赖筛选,过滤掉那些虽然类型匹配但是beanName和名称不匹配的Bean。

@Resource真的只是按名称来进行依赖注入吗?@Autowired真的只是按照类型来依赖注入吗?相关推荐

  1. SpringMVC:学习笔记(11)——依赖注入与@Autowired

    SpringMVC:学习笔记(11)--依赖注入与@Autowired 使用@Autowired 从Spring2.5开始,它引入了一种全新的依赖注入方式,即通过@Autowired注解.这个注解允许 ...

  2. maven 公共模块依赖_「spring-boot 源码解析」spring-boot 依赖管理

    问题 maven 工程,依赖管理是非常基本又非常重要的功能,现在的工程越来越庞大,依赖越来越多,各种二方包.三方包太多太多,依赖冲突处理起来真是让人头疼,经常需要涉及到多个地方需要调整. 微信公众号: ...

  3. 【spring】依赖注入之@Autowired依赖注入

    @Autowired依赖注入 本文源码基于spring-framework-5.3.10. 源码位置:org.springframework.beans.factory.annotation.Auto ...

  4. 依赖倒置原则——举例说明Java设计模式中的依赖倒置原则

    依赖倒置原则--举例说明Java设计模式中的依赖倒置原则 一.前言 看官方定义 二.举例说明 2.1 例子介绍(无聊的可看看,着急的请跳过) 2.2 反例 2.1.1 反例1 (1)类图说明 (2)代 ...

  5. 当我不再依赖你的时候说说_不会再依赖你的说说 以后不再依赖别人的说说

    1.岁月让自己学会了担当,学会了疗伤,不再依赖别人.面对挫折,不再推卸责任不再找借口.岁月让自己学会了选择与放弃.在纷繁复杂的事物中理清什么是主什么是次,哪些是轻哪些是重.自省更自信,自尊更自爱.坚信 ...

  6. java源码依赖分析_高德APP全链路源码依赖分析工程

    一.背景 高德 App 经过多年的发展,其代码量已达到数百万行级别,支撑了高德地图复杂的业务功能.但与此同时,随着团队的扩张和业务的复杂化,越来越碎片化的代码以及代码之间复杂的依赖关系带来诸多维护性问 ...

  7. 依赖项出现感叹号怎么办_SpringBoot中如何对依赖进行管理?

    SpringBoot中的起步依赖(starter)是一组特定功能的依赖项集合,SpringBoot通过starter来进行项目的依赖管理,而不是直接基于单独的依赖项来进行依赖管理. starter其实 ...

  8. Racket 6.11提供了稳定的细化类型和依赖函数特性

    Typed Racket是Racket语言的一种静态类型方言.Racket 6.11为Typed Racket提供了细化类型(Refinement Type)和依赖函数(Dependent Funct ...

  9. 依赖注入是什么?Go是如何实现依赖注入的?

    什么是依赖注入 依赖注入的好处 Go的依赖注入-wire 依赖注入是什么? 第一次听到这个词的时候我是一脸懵逼的,很拗口有没有,可能很多学过spring的同学觉得这是很基础很好理解的知识,但因为我之前 ...

  10. 使用Eclipse发布一个依赖于其他项目的java项目,被依赖的项目不能自动编译,因而引发notfoundClass的异常。...

    编号 007 错误.问题类型 java 开发 描述 使用Eclipse发布一个依赖于其他项目的java项目,被依赖的项目不能自动编译,因而引发notfoundClass的异常. 解决方案 选择主项目点 ...

最新文章

  1. 4.4 使用STM32控制MC20进行GPS帧数据解析
  2. python基础学习(十二)变量进阶
  3. Jade —— 源于 Node.js 的 HTML 模板引擎
  4. NHibernate:no persister for 异常
  5. BZOJ 4810 [Ynoi2017]由乃的玉米田(莫队+bitset)
  6. python 字符串匹配like_python中关于正则表达式一
  7. openssl、gmssl的简单介绍
  8. java面试题(仅供参考)
  9. ACEL计算机证书,FSHW:酪蛋白水解物衍生的双功能肽的体外和计算机分析
  10. win7上搭建ftp站点
  11. 山西大同大学计算机考试打印准考证
  12. 网络广告中各种广告形式
  13. Qt实现类似QQ头像
  14. ldc java_java – LDC指令代码的负值是什么意思?
  15. 9月29更新美版T-mobile版本iPhone7代和7P有锁机卡贴解锁方法
  16. Vagrant在,win7/win10系统下搭建使用
  17. 去掉搜狗拼音烦人的x+;进入搜狗搜索
  18. vue3.0 结合element ui 开发后台ui框架
  19. iOS7+系统自带条码扫描
  20. 利用Python创建文件

热门文章

  1. net core mysql开源框架_.net core 基于Dapper 的分库分表开源框架(core-data)
  2. python月份字符串_python实现字符串和日期相互转换的方法
  3. 用计算机弹假面骑士build,假面骑士build使用的武器汇总,你知道几个?
  4. ML Case Studies(0)
  5. TCP端口扫描[Python3.5]
  6. 高德地图android显示级别指定位置,获取地图中心点/级别
  7. 2021-09-03DIEN分成两步去抓取用户的兴趣演化:1兴趣抽取层 去抽取基于用户行为序列的兴趣序列2兴趣演化层 跟target item相关
  8. 122.买卖股票的最佳时机II
  9. 自定义优先队列的元素权重
  10. 通过函数指针实现四则运算