Day603.Bean选取问题&找不到问题 -Spring编程常见错误
Posted 阿昌喜欢吃黄桃
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day603.Bean选取问题&找不到问题 -Spring编程常见错误相关的知识,希望对你有一定的参考价值。
Spring编程常见错误#Bean选取问题&找不到问题
当讲到Spring,那第一会想到依赖注入、控制反转
。
那必然会想到一个从ioc容器拿到bean的注解@Autowired
,他是用来支持依赖注入的核心利器之一。
那以下列举,在使用@Autowired注解会出现的一些小问题。
一、当Bean选择过多时
不管是Spring菜鸡还是大神,都会遇到过Spring的问题:↓
这问题就是说这个Bean是单例的,但是找到了两个。
required a single bean, but 2 were found
当如下,在ioc容器放入,DataService接口的两个Bean
public interface DataService
void deleteStudent(int id);
//#1
@Repository
@Slf4j
public class OracleDataService implements DataService
@Override
public void deleteStudent(int id)
log.info("delete student info maintained by oracle");
//#2
@Repository
@Slf4j
public class CassandraDataService implements DataService
@Override
public void deleteStudent(int id)
log.info("delete student info maintained by cassandra");
那在启动spring的时注入初始化的时候就会报上面的错误
当这种情况时,Spring无法默认的给出在两个bean选哪个作为DataService的实现Bean放入ioc容器中
那我们如果不给出方案策略,那就会报required a single bean, but 2 were found
的错。
如下是源码如何觉得选择哪个进行注入的方案:
- 判断是否有
@Primary
,进行主要选择策略 - 判断是否
@Priority
,进行优先级策略 - 判断根据
Bean 名字
的严格匹配来决策 - 最后返回 null,告知无法决策出哪种最合适
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 名字
// 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
,进行优先级策略- 使用默认名字匹配,或使用
@Qualifier
指定选择哪个
二、选取Bean时首字母大小写问题
若我们在上面的问题中使用了@Qualifier
的方式去解决问题,那么就会有可能出 选取Bean时首字母大小写问题
@Autowired()
@Qualifier("cassandraDataService")
DataService dataService;
但是我们在给写上CassandraDataService
的情况就会出现找不到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 的名字,如果没有显式指明,就应该是类名,不过首字母应该小写
但是这个类是有两个大写开头以上
,那么策略就会改变,若针对SQLiteDataService
类来说使用这种规则sQLiteDataService
就会出错
@Autowired
@Qualifier("sQLiteDataService")
DataService dataService;
那为什么吗?Spring在生成对应具体类的beanname的方式源码如下
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);
我们看到了,首先他会判断name的长度是否大于1,且第一个&第二个字母是否是大写,如果是就直接返回。不然就走下面将首字母改为小写返回
总结: 如果一个类名是以两个大写字母开头的,则首字母不变,其它情况下默认首字母变成小写。
那自然有了解决方案:
- 根据如上的规则使用
@Qualifier
正确的指定,如@Qualifier("cassandraDataService")
或@Qualifier("SQLiteDataService")
- 显示的在类头上的
@Repository
上去显示的指明,如@Repository("CassandraDataService")
总结:如果你不太了解源码,不想纠结于首字母到底是大写还是小写,建议你用第二种方法去避免困扰。
三、内部类的 Bean 指定问题
当要一引用一个类中的类,也就是内部类的情况下,又会有不一样的问题
public class StudentController
//StudentController的内部类
@Repository
public static class InnerClassDataService implements DataService
@Override
public void deleteStudent(int id)
//空实现
那如果在别处的指定bean名使用如下,也会出现找不到bean,那为什么呢?
@Autowired
@Qualifier("innerClassDataService")
DataService innerClassDataService;
当时我们只关注了首字母是否小写的代码片段,而在最后变换首字母之前,有一行语句是对 class 名字的处理,代码如下:
String shortClassName = ClassUtils.getShortName(beanClassName);
我们可以看下它的实现,参考 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;
那如上的类,就会经历三次的改变
- com.spring.puzzle.class2.example3.StudentController.InnerClassDataService
- StudentController.InnerClassDataService
- studentController$InnerClassDataService
那最后就是studentController$InnerClassDataService
,所以就应该写如下,如果要指定一个类的内部类的情况
@Autowired
@Qualifier("studentController$InnerClassDataService")
DataService innerClassDataService;
那最后,如果我想要直接用@Autowired
,不去组合@Qualifier,直接来个一步到位???
去明确指定一个内部类,如下写法ok吗?
@Autowired
DataService studentController.InnerClassDataService;
答案是不行的
,编译都会过不了
以上是关于Day603.Bean选取问题&找不到问题 -Spring编程常见错误的主要内容,如果未能解决你的问题,请参考以下文章
长沙雅礼中学集训-------------------day1(内含day0)