目录
1.字段注入
@Autowired
private UserDao userDao;
2.构造器注入
@Component
public class UserService {
private UserDao userDao;
@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
3.setter注入
@Component
public class UserService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
如果我们的两个Bean循环依赖,就会抛出该异常
Error creating bean with name 'beanOne': Requested bean is currently in creation: ls there an
unresolvable circular reference?
@Component
public class UserService {
private OrderService orderService;
@Autowired
public UserService(OrderService orderService){
this.orderService=orderService;
}
}
@Component
public class OrderService {
private UserService userService;
@Autowired
public OrderService(UserService userService){
this.userService=userService;
}
}
如果出现两个类循环引用彼此,说明代码的设计存在问题,如果临时无法解决可以使用以下两种解决方法:
1.使用@Lazy注解设置为懒加载,令其中一个类延迟初始化,使得它在首次使用时才进行初始化,OrderService在初始化时可以不需要UserService的实例,避免了循环依赖的问题
@Component
@Lazy
public class UserService {
private OrderService orderService;
@Autowired
public UserService(@Lazy OrderService orderService) {
this.orderService = orderService;
}
}
@Component
public class OrderService {
private UserService userService;
@Autowired
public OrderService(UserService userService) {
this.userService = userService;
}
}
2.使用setter注入,将构造器注入修改为setter注入,这样可以确保构造函数的参数已经被完全初始化,避免了循环依赖的问题。
@Component
public class UserService {
private OrderService orderService;
public UserService() {}
@Autowired
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
}
@Component
public class OrderService {
private UserService userService;
public OrderService() {}
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
Spring为何不建议使用基于字段的依赖注入
当你使用字段注入IDEA会给你如下警告
spring官网文档也指出强制依赖使用构造函数注入,可选依赖使用setter注入
官网文档地址:
使用字段注入会带来如下问题:
我们都知道,根据SOLID设计原则来进,一个类的设计应该符合单一职责原则,就是一个类只能做一件功能,当我们使用基于字段注入的时候,随着业务的暴增,字段越来越多,我们是很难发现我们已经默默中违背了单一职责原则的。
但是如果我们使用基于构造器注入的方式,因为构造器注入的写法比较臃肿,所以它就在间接提醒我们违背了单一职责原则,该做重构了
//基于字段注入
@Component
public class OrderService {
@Autowired
private UserService userService;
@Autowired
private GoodsService goodsService;
}
//基于构造器的注入
@Component
public class OrderService {
private UserService userService;
private GoodsService goodsService;
@Autowired
public OrderService(UserService userService ,GoodsService goodsService){
this.userService=userService;
this.goodsService=goodsService;
}
}
对于一个bean来说,它的初始化顺序为:静态变量或静态语句块 ->实例变量或初始化语句块 -> 构造方法 -> @Autowired。
所以,在静态语句块,初始化语句块,构造方法中使用Autowired表明的字段,都会引起NPE问题
@Component
public class OrderService {
@Autowired
private UserService userService;
private String orderId;
public OrderService(){
//此时的OrderService还未初始化,会抛出空指针异常
this.orderId= userService.getUserId();
}
}
相反的,用构造器的依赖注入,就会实例化对象,在使用的过程中字段一定不为空
对于一个正常的使用依赖注入的Bean来说,它应该“显式”的通知容器,自己需要哪些Bean,可以通过构造器通知,public的setter方法通知,这些设计都是没问题的。
外部容器不应该感知到Bean内部私有字段(如上例中的private UserService)的存在,私有字段对外部应该是不可见的。由于私有字段不可见,所以在设计层面,我们不应该通过字段注入的方式将依赖注入到私有字段中。这样会破坏封装性。
所以,当我们对字段做注入的时候,Spring就需要关心一个本来被我们封装到一个bean中的私有成员变量,这就和他的封装性违背了。因为我们应该通过setter或者构造函数来修改一个字段的值。
很明显,使用了Autowired注解,说明这个类依赖了Spring容器,这让我们在进行UT的时候必须要启动个Spring容器才可以测试这个类,显然太麻烦,这种测试方式非常重,对于大型项目来说,往往启动一个容器就要好几分钟,这样非常耽误时间。
不过,如果使用构造器的依赖注入就不会有这种问题,或者,我们可以使用Resource注解(@Resource是Java EE提供的注解,更加通用且符合J2EE标准,可以在不同的Java EE容器中使用)也可以解决上述问题。