二、Spring Bean 依赖注入常见错误(上)
提及 Spring 的优势或特性,我们都会立马想起“控制反转、依赖注入”这八字真言。而 @Autowired 正是用来支持依赖注入的核心利器之一。表面上看,它仅仅是一个注解,在使用上不应该出错。但是,在实际使用中,我们仍然会出现各式各样的错误,而且都堪称经典。
一.过多赠予,无所适从
1、代码:一个service有两个实现类
1.Controller
@RestController
@Slf4j
@Validated
public class StudentController {
@Autowired
DataService dataService;
@RequestMapping(path = "students/{id}", method = RequestMethod.DELETE)
public void deleteStudent(@PathVariable("id") @Range(min = 1,max = 100) int id){
dataService.deleteStudent(id);
};
}
2.Service接口
public interface DataService {
void deleteStudent(int id);
}
3.DataService实现类OracleDataService
@Repository
@Slf4j
public class OracleDataService implements DataService{
@Override
public void deleteStudent(int id) {
log.info("delete student info maintained by oracle");
}
}
4.DataService实现类CassandraDataService
@Repository
@Slf4j
public class CassandraDataService implements DataService{
@Override
public void deleteStudent(int id) {
log.info("delete student info maintained by cassandra");
}
}
2、案例解析
上面的代码在启动的时候会报错,因为一个service有两个实现类,仅仅依靠@Autowired,容器不知道具体调用的是哪个。
3、解决方法
1.设置优先选择@Primary
@Repository
@Primary
@Slf4j
public class OracleDataService implements DataService{
//省略非关键代码
}
2.@Qualifier 来显式指定引用
@Autowired()
@Qualifier("cassandraDataService")
DataService dataService;
二.显式引用 Bean 时首字母忽略大小写
在使用 @Qualifier 时,我们有时候会犯另一个经典的小错误,就是我们可能会忽略 Bean 的名称首字母大小写。
但是仅仅是注意首字母大小写,而不清楚具体的实现规则,就还是可能会报错。
如果Bean的名称已经指定,那么使用的就是指定的名称。
如果Bean的名称未指定,如果一个类名是以两个大写字母开头的,则首字母不变,其它情况下默认首字母变成小写。结合我们之前的案例,SQLiteDataService 的 Bean,其名称应该就是类名本身,而 CassandraDataService 的 Bean 名称则变成了首字母小写(cassandraDataService)。
三.引用内部类的 Bean 遗忘类名
1、代码:在类的内部声明了一个BEAN对象
public class StudentController {
@Repository
public static class InnerClassDataService implements DataService{
@Override
public void deleteStudent(int id) {
//空实现
}
}
//省略其他非关键代码
}
2、案例解析
如果这样去加载这个BEAN是无法加载成功的。
@Autowired
@Qualifier("innerClassDataService")
DataService innerClassDataService;
虽然BEAN仍然在容器中,但是其名称并不是innerClassDataService。
3、问题修正
1.@Qualifier指定
@Autowired
@Qualifier("studentController.InnerClassDataService")
DataService innerClassDataService;
2.如果没有多个实现类,可以直接使用@Autowired
@Autowired
DataService innerClassDataService;
四.@Autowire为空(new出来的对象,其内部的@Autowire为空)
1、代码分析
以以下代码为例
@Component
public class ParserManager {
private Logger logger = LoggerFactory.getLogger(ParserManager.class);
@Autowired
StDeviceMapper stDeviceMapper;
// //
@Autowired
WlOrigindataMapper originDataMapper;
如果调用ParserManager 类的时候,创建方式为
ParserManager parserManager =new ParserManager ();
那么里面的@Autowired均为空
@Autowired
StDeviceMapper stDeviceMapper;
// //
@Autowired
WlOrigindataMapper originDataMapper;
2、案例解析
可能导致为null的原因
- 该类没有托管给spring 管理,一般在类的上面添加@Component
- 这个类有被new出来的实例的,new 过的对象不会交给Spring容器管理 所以里面的
service或者dao注入不进来。一般是指引用某些框架,你是继承某个接口,但是这些框架默认new过这个方法,比如MVC拦截的HandlerInterceptor类。
3、解决方式
加上@Component注解
追溯最初的调用创建方式。