对于同一个接口存在多个实现类,此时使用@Autowired
注解会出现required a single bean, but 2 were found
// 控制器类
@RestController
@Slf4j
@Validated
public class StudentController {
@Autowired
DataService dataService;
@Autowired
DataService oracleDataService;
@RequestMapping(path = "students/{id}", method = RequestMethod.DELETE)
public void deleteStudent(@PathVariable("id") @Range(min = 1,max = 100) int id){
dataService.deleteStudent(id);
};
}
// DataService接口
public interface DataService {
void deleteStudent(int id);
}
// 接口的具体实现
@Repository
@Slf4j
public class OracleDataService implements DataService {
@Override
public void deleteStudent(int id) {
log.info("delete student info maintained by 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
属性成员)
// AutowiredAnnotationBeanPostProcessor#postProcessProperties
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
// AutowiredFieldElement#inject
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
...
try {
// *寻找依赖,desc为dataService的DependencyDescriptor
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
...
if (value != null) {
ReflectionUtils.makeAccessible(field);
// 装配依赖
field.set(bean, value);
}
}
在注入时发现存在两个匹配的Bean,此时需要进行判断:
// DefaultListableBeanFactory#doResolveDependency(即beanFactory.resolveDependency的底层逻辑)
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter)
throws BeansException {
try {
...
if (matchingBeans.size() > 1) {
// *Ⅰ、首先依次按照@Primary、@Priority和Bean名字来匹配准确的Bean(该步也是后续提供解决方式的关键)
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
// *Ⅱ、如果选择不出来则判断是否满足以下两个条件:
// ①@Autowired要求是必须注入的(即required保持默认值为true)
// ②注解的属性类型不是可接受多个Bean的类型,例如数组、Map、集合
if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
// 执行到该步骤的话就是抛出异常了
return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
}
else {
return null;
}
}
instanceCandidate = matchingBeans.get(autowiredBeanName);
}
else {
// 只匹配到一个(即只有一个实现类)
Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
autowiredBeanName = entry.getKey();
instanceCandidate = entry.getValue();
}
}
}
@Primary
、@Priority
和Bean名字来匹配准确的Bean:// DefaultListableBeanFactory#determineAutowireCandidate
protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
Class<?> requiredType = descriptor.getDependencyType();
// ①先根据@Primary来决策
String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
if (primaryCandidate != null) {
return primaryCandidate;
}
// ②再根据@Priority决策
String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
if (priorityCandidate != null) {
return priorityCandidate;
}
// ③最后尝试根据Bean名字的严格匹配来决策
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;
}
@Autowired
要求是必须注入的和解的属性类型不是可接受多个Bean的类型:// DefaultListableBeanFactory#indicatesMultipleBeans
private boolean indicatesMultipleBeans(Class<?> type) {
return (type.isArray() || (type.isInterface() &&
(Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type))));
}
使上述分析的两个情况其中一个不成立即可,但是需要根据业务需求进行选择,比如使用标记@Primary标记实现类,虽然可避免报错但是不能使用多个@Primary()
注解标记同一个类型的bean,所以可进行BeanName的精确匹配:
// 此时需要使用Oracle
@Autowired
DataService oracleDataService;
在第一个案例中还可以使用@Qualifier
显式指定引用的是哪种服务,但是在使用的时候可能会忽略Bean的名称首字母大小写
CassandraDataService
类,如果选择指定为@Qualifier("CassandraDataService")
会报错@Autowired()
@Qualifier("CassandraDataService") // 报错,这里需要写为cassandraDataService
DataService dataService;
SQLiteDataService
类,如果选择指定为@Qualifier("sQLiteDataService")
会报错为什么有些类首字母需要大写,而有些则需要小写?
在SpringBoot启动时会自动扫描当前的Package,进而找到直接或间接标记了@Component
的Bean的定义(即BeanDefinition
),找出这些Bean的信息就可生成这些Bean的名字,然后组合成一个个BeanDefinitionHolder
返回给上层
// ClassPathBeanDefinitionScanner#doScan
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// *生成Bean的名字
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
在生成Bean的名字时有两种生成方式:
CassandraDataService
的@Repository
注解中写名称)则用显式名称// AnnotationBeanNameGenerator#generateBeanName
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
if (definition instanceof AnnotatedBeanDefinition) {
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {
return beanName;
}
}
// *生成默认名称
return buildDefaultBeanName(definition, registry);
}
// AnnotationBeanNameGenerator#buildDefaultBeanName
// 默认名称生成方法
protected String buildDefaultBeanName(BeanDefinition definition) {
String beanClassName = definition.getBeanClassName();
Assert.state(beanClassName != null, "No bean class name set");
String shortClassName = ClassUtils.getShortName(beanClassName);
// *设置首字母的规则
return Introspector.decapitalize(shortClassName);
}
// Introspector#decapitalize
// 设置首字母的规则:如果一个类名是以两个大写字母开头的则首字母不变,其它情况下默认首字母变成小写
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))){
return name;
}
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
分析后原因在于decapitalize
方法中的设置Bean名称首字母的规则:如果一个类名是以两个大写字母开头的则首字母不变,其它情况下默认首字母变成小写
两种解决方式:
@Autowired
@Qualifier("cassandraDataService") // 第二个字母为小写就只需将首字母小写
DataService dataService;
@Autowired
@Qualifier("SQLiteDataService") // 第二个字母为大写首字母依旧保持大写
DataService dataService;
@Repository("CassandraDataService") // 显示定义
@Slf4j
public class CassandraDataService implements DataService {
@Override
public void deleteStudent(int id) {
log.info("delete student info maintained by cassandra");
}
}
假设直接使用内部类来定义一个DataService
接口的实现类,并且根据上面案例的经验对Bean名称按规定书写,还是出现了报错:
@Repository
public static class InnerClassDataService implements DataService{
@Override
public void deleteStudent(int id) {
...
}
}
@Autowired
@Qualifier("innerClassDataService")
DataService innerClassDataService;
在进行首字母变换前还进行了对class名称的处理:
// AnnotationBeanNameGenerator#buildDefaultBeanName
protected String buildDefaultBeanName(BeanDefinition definition) {
String beanClassName = definition.getBeanClassName();
Assert.state(beanClassName != null, "No bean class name set");
// 对class名称的处理
String shortClassName = ClassUtils.getShortName(beanClassName);
return Introspector.decapitalize(shortClassName);
}
// ClassUtils#getShortName
public static String getShortName(String className) {
Assert.hasLength(className, "Class name must not be empty");
int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
int nameEndIndex = className.indexOf(CGLIB_CLASS_SEPARATOR);
if (nameEndIndex == -1) {
nameEndIndex = className.length();
}
String shortName = className.substring(lastDotIndex + 1, nameEndIndex);
shortName = shortName.replace(INNER_CLASS_SEPARATOR, PACKAGE_SEPARATOR);
return shortName;
}
对于内部类的className=com.spring.puzzle.class2.example3.StudentController$InnerClassDataService
,经过上述逻辑后为得到的类名为StudentController.InnerClassDataService
,对它进行首字母转换后得到studentController.InnerClassDataService
,与预期结果不符
按照其对内部类转换后的类名写即可:
@Autowired
@Qualifier("studentController.InnerClassDataService")
DataService innerClassDataService;
极客时间-Spring 编程常见错误 50 例
https://github.com/jiafu1115/springissue/tree/master/src/main/java/com/spring/puzzle/class2