1 做不到雨露均沾
经常会遇到,required a single bean, but 2 were found。
- 根据ID移除学生
DataService是个接口,其实现依赖Oracle:
现在期望把部分非核心业务从Oracle迁移到Cassandra,自然会先添加上一个新的DataService实现:
@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属性成员。
“填充”过程的关键就是执行各种BeanPostProcessor处理器,关键代码如下:
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。
装配过程:
- 寻找所有需依赖注入的字段和方法: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 = 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)))); }
案例程序能满足这些条件,所以报错并不奇怪。而如果我们把这些条件想得简单点,或许更容易帮助我们去理解这个设计。就像我们遭遇多个无法比较优劣的选择,却必须选择其一时,与其偷偷地随便选择一种,还不如直接报错,起码可以避免更严重的问题发生。