高斋晓开卷,独共圣人语。这篇文章主要讲述#yyds干货盘点# Spring的@Autowired依赖注入原来这么多坑!相关的知识,希望能为你提供帮助。
1 做不到雨露均沾经常会遇到,required a single bean, but 2 were found。
- 根据ID移除学生
DataService是个接口,其实现依赖Oracle:
@Repository
@Slf4j
public class CassandraDataService implements DataService
@Override
public void deleteStudent(int id)
log.info("delete student info maintained by cassandra");
当完成支持多个数据库的准备工作时,程序就已经无法启动了,报错如下:
解析当一个Bean被构建时的核心步骤:
- 执行AbstractAutowireCapableBeanFactory#createBeanInstance:通过构造器反射出该Bean,如构建StudentController实例
- 执行AbstractAutowireCapableBeanFactory#populate:填充设置该Bean,如设置StudentController实例中被 @Autowired 标记的dataService属性成员。
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw)
//省略非关键代码
for (BeanPostProcessor bp : getBeanPostProcessors())
if (bp instanceof InstantiationAwareBeanPostProcessor)
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
//省略非关键代码
因为StudentController含标记为Autowired的成员属性dataService,所以会使用到AutowiredAnnotationBeanPostProcessor完成“装配”:找出合适的DataService bean,设置给StudentController#dataService。
【#yyds干货盘点# Spring的@Autowired依赖注入原来这么多坑!】装配过程:
- 寻找所有需依赖注入的字段和方法:AutowiredAnnotationBeanPostProcessor#postProcessProperties
- 根据依赖信息寻找依赖并完成注入。比如字段注入,参考AutowiredFieldElement#inject方法:
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable
Field field = (Field) this.member;
Object value;
// ...
try
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
// 寻找“依赖”,desc为"dataService"的DependencyDescriptor
value = https://www.songbingjia.com/android/beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
// ...
if (value != null)
ReflectionUtils.makeAccessible(field);
// 装配“依赖”
field.set(bean, value);
案例中的错误就发生在上述“寻找依赖”的过程中,DefaultListableBeanFactory#doResolveDependency
当根据DataService类型找依赖时,会找出2个依赖:
- CassandraDataService
- OracleDataService
- 调用determineAutowireCandidate方法来选出优先级最高的依赖,但是发现并没有优先级可依据。具体选择过程可参考
DefaultListableBeanFactory#determineAutowireCandidate:
protected String determineAutowireCandidate(Map< String, Object> candidates, DependencyDescriptor descriptor)
Class< ?> requiredType = descriptor.getDependencyType();
String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
if (primaryCandidate != null)
return primaryCandidate;
String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
if (priorityCandidate != null)
return priorityCandidate;
// Fallback
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;
优先级的决策是先根据@Primary,其次是@Priority,最后根据Bean名严格匹配。
如果这些帮助决策优先级的注解都没有被使用,名字也不精确匹配,则返回null,告知无法决策出哪种最合适。
@Autowired要求是必须注入的(required默认值true),或注解的属性类型并不是可以接受多个Bean的类型,例如数组、Map、集合。
这点可以参考DefaultListableBeanFactory#indicatesMultipleBeans:
private boolean indicatesMultipleBeans(Class< ?> type)
return (type.isArray() || (type.isInterface() & &
(Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type))));
案例程序能满足这些条件,所以报错并不奇怪。而如果我们把这些条件想得简单点,或许更容易帮助我们去理解这个设计。就像我们遭遇多个无法比较优劣的选择,却必须选择其一时,与其偷偷地随便选择一种,还不如直接报错,起码可以避免更严重的问题发生。
修正打破上述两个条件中的任何一个即可,即让候选项具有优先级或根本不选择。
但并非每种条件的打破都满足实际需求:
如可以通过使用**@Primary**让被标记的候选者有更高优先级,但并不一定符合业务需求,好比我们本身需要两种DB都能使用,而非不可兼得。
@Repository
@Primary
@Slf4j
public class OracleDataService implements DataService
//省略非关键代码
要同时支持多种DataService,不同情景精确匹配不同的DataService,可这样修改:
@Autowired
DataService oracleDataService;
将属性名和Bean名精确匹配,就能实现完美的注入选择:
- 需要Oracle时指定属性名为oracleDataService
- 需要Cassandra时则指定属性名为cassandraDataService
@Autowired()
@Qualifier("cassandraDataService")
DataService dataService;
这样能让寻找出的Bean只有一个(即精确匹配),无需后续的决策过程:
DefaultListableBeanFactory#doResolveDependency
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set< String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException
//省略其他非关键代码
//寻找bean过程
Map< String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty())
if (isRequired(descriptor))
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
return null;
//省略其他非关键代码
if (matchingBeans.size() > 1)
//省略多个bean的决策过程,即案例1重点介绍内容
//省略其他非关键代码
使用 @Qualifier 指定名称匹配,最终只找到唯一一个。但使用时,可能会忽略Bean名称首字母大小写。
如:
@Autowired
@Qualifier("CassandraDataService")
DataService dataService;
运行报错:
Exception encountered during context initialization - cancelling refresh
attempt: org.springframework.beans.factory.UnsatisfiedDependencyException:
推荐阅读
- #yyds干货盘点#web安全day47(口令字典crunchcupphydraMSF-psexec_psh的使用)
- 全网最详细之pt-osc 处理MySQL外键表流程分析
- Kubernetes官方java客户端之六(OpenAPI基本操作)
- Spring认证中国教育管理中心-Spring Data Couchbase教程九
- 特定页面模板的WP自定义元框
- WP(向WooCommerce单一产品添加div)
- WordPress-wp_list_authors可获取多个作者图像和简历
- WordPress wp_list_categories()格式
- WordPress WP_Customize_Image_Control()无法使用icon(.ico)文件