1.多个实现类 如何匹配
在实际的开发中,我们会使用@Autowired 注解进行依赖注入对应的bean,但是如果我们依赖的是一个接口,有对应多个实现的话,就会出现异常。
@RestController
public class DbController {
@Autowired
private DbService dbService;
@RequestMapping(path = "/hiDb",method = RequestMethod.GET)
public String hiScope() {
dbService.db();
return "";
}
}
@Service
public class MySQLDbService implements DbService{
@Override
public void db() {
System.out.println("mysql init");
}
}
@Service
public class HbaseDbService implements DbService{
@Override
public void db() {
System.out.println("HbaseDB");
}
}
Field dbService in com.qxlx.spingboot.controller.DbController required a single bean, but 2 were found:
- hbaseDbService: defined in file [/Users/qxlx/work/qxlx/qxlx/spring/target/classes/com/qxlx/spingboot/service/HbaseDbService.class]
- mySQLDbService: defined in file [/Users/qxlx/work/qxlx/qxlx/spring/target/classes/com/qxlx/spingboot/service/MySQLDbService.class]
原理分析
核心源码解析
AutowiredAnnotationBeanPostProcessor
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
// 找出依赖的字段和方法 构建元数据 通过递归的方式进行
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
// 完成对方法或者字段的注入 执行注入 将依赖注入到bean
metadata.inject(bean, beanName, pvs);
return pvs;
}
if (matchingBeans.size() > 1) {
// 根据1.优先级@Primay来决策 2.@prority 3.bean名字
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
// @Autowired是必须注入的, 注解的属性类型并不是可以接受的多个Bean类型
if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
}
else {
return null;
}
}
}
其实就是匹配多个条件,都不满足的时候,就抛出异常。因为没有办法决定使用那一个bean
解决方案
第一种 要么就是直接指定。
@Autowired
private DbService mySQLDbService;
第二种指定
@Autowired
@Qualifier("mySQLDbService")
private DbService dbService;
2.显示依赖bean首字母大小写 demo
但是如果我们故意写错了,回出现异常。
@Autowired
@Qualifier("MySQLDbService")
private DbService dbService;
异常信息
No qualifying bean of type 'com.qxlx.spingboot.service.DbService' 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=MySQLDbService)}
具体原因
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
// 如果是注解BD
if (definition instanceof AnnotatedBeanDefinition) {
// 查看bean是否有有name 有返回
String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
if (StringUtils.hasText(beanName)) {
// Explicit bean name found.
return beanName;
}
}
// 设置一个默认的名称
// Fallback: generate a unique default bean name.
return buildDefaultBeanName(definition, registry);
}
protected String buildDefaultBeanName(BeanDefinition definition) {
// 获取BeanClassName
String beanClassName = definition.getBeanClassName();
Assert.state(beanClassName != null, "No bean class name set");
// 获取类名
String shortClassName = ClassUtils.getShortName(beanClassName);
// 生成类名
return Introspector.decapitalize(shortClassName);
}
public static String decapitalize(String name) {
// 基本验证
if (name == null || name.length() == 0) {
return name;
}
// 如果类名第一个和第二个都是大写 返回原来的类名
// 比如 如果是MYSQLService 返回的就是MYSQLService
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))){
return name;
}
char[] chars = name.toCharArray();
//将第一个字母小写
// 比如MySQLService 返回的就是mySQLService
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
根据上述源码的分析,可以找到,在生成一个类名的时候,其实是有几个原则
1.如果类名提供了,那么按照对应的名称查找。
2.如果类名没有提供,并且前两个字母都是大写,那么默认就是原来的类名
3.类名是第一个字母小写。
掌握了上述原理,就可以轻松应对这些问题了。
3.引用内部类的bean类名
通过上面的案例,可以知道了beanName的规则,但是对于内部类来说。
@Service
public class MySQLDbService implements DbService{
@Override
public void db() {
System.out.println("mysql init");
}
@Service
public static class MySQLDbServiceImpl implements DbService{
@Override
public void db() {
System.out.println("mysql init");
}
}
}
@Autowired
@Qualifier("MySQLDbService.MySQLDbServiceImpl")
private DbService dbService;
发现出现错误。
No qualifying bean of type 'com.qxlx.spingboot.service.DbService' 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=MySQLDbService.MySQLDbServiceImpl)}
原理分析
在生成类名的时候,有一个方法
String shortClassName = ClassUtils.getShortName(beanClassName);
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(NESTED_CLASS_SEPARATOR, PACKAGE_SEPARATOR);
return shortName;
}