修正
打破上述两个条件中的任何一个即可,即让候选项具有优先级或根本不选择。
但并非每种条件的打破都满足实际需求:
如可以通过使用**@Primary**让被标记的候选者有更高优先级,但并不一定符合业务需求,好比我们本身需要两种DB都能使用,而非不可兼得。
@Repository @Primary @Slf4j public class OracleDataService implements DataService{ //省略非关键代码 }
要同时支持多种DataService,不同情景精确匹配不同的DataService,可这样修改:
@Autowired DataService oracleDataService;
将属性名和Bean名精确匹配,就能实现完美的注入选择:
- 需要Oracle时指定属性名为oracleDataService
- 需要Cassandra时则指定属性名为cassandraDataService
显式引用Bean时首字母忽略大小写
还有另外一种解决办法,即采用@Qualifier显式指定引用服务,例如采用下面的方式:
@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: Error creating bean with name 'studentController': Unsatisfied dependency expressed through field 'dataService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.spring.puzzle.class2.example2.DataService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value=CassandraDataService)}
若未显式指定 bean 名称,默认就是类名,不过首字母小写!
假设要支持SQLServer,定义了一个名为SQLServerDataService的实现:
@Autowired @Qualifier("sQLServerDataService") DataService dataService;
依然出现之前错误,而若改成SQLServerDataService,则运行通过。
这真是疯了呀!
显式引用Bean时,首字母到底是大写还是小写?
答疑
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
当因名称问题(例如引用Bean首字母搞错了)找不到Bean,会抛NoSuchBeanDefinitionException。
不显式设置名字的Bean,其默认名称首字母到底是大写还是小写呢?
Spring Boot应用会自动扫包,找出直接或间接标记了 @Component 的BeanDefinition。例如CassandraDataService、SQLServerDataService都被标记了@Repository,而Repository本身被@Component标记,所以都间接标记了@Component。
一旦找出这些Bean信息,就可生成Bean名,然后组合成一个个BeanDefinitionHolder返回给上层:
ClassPathBeanDefinitionScanner#doScan
BeanNameGenerator#generateBeanName产生Bean名,有两种实现方式:
因为DataService实现都是使用注解,所以Bean名称的生成逻辑最终调用的其实是
AnnotationBeanNameGenerator#generateBeanName
看Bean有无显式指明名称,若:
- 有
用显式名称 - 没有
生成默认名称
案例没有给Bean指名,所以生成默认名称,通过方法:
buildDefaultBeanName
首先,获取一个简短的ClassName,然后调用Introspector#decapitalize方法,设置首字母大写或小写,具体参考下面的代码实现:
- 一个类名是以两个大写字母开头,则首字母不变
- 其它情况下默认首字母变成小写
SQLServerDataService的Bean,其名称应该就是类名本身,而CassandraDataService的Bean名称则变成了首字母小写(cassandraDataService)。