spring 依赖注入
1. 几种注入方式的对比
基于 field 注入的坏处
成也萧何败也萧何
基于 field 注入虽然简单,但是却会引发很多的问题。这些问题在我平常开发阅读项目代码的时候就经常遇见。
容易违背了单一职责原则 使用这种基于 field 注入的方式,添加依赖是很简单的,就算你的类中有十几个依赖你可能都觉得没有什么问题,普通的开发者很可能会无意识地给一个类添加很多的依赖。但是当使用构造器方式注入,到了某个特定的点,构造器中的参数变得太多以至于很明显地发现 something is wrong。拥有太多的依赖通常意味着你的类要承担更多的责任,明显违背了单一职责原则(SRP:Single responsibility principle)。
依赖注入与容器本身耦合依赖注入框架的核心思想之一就是受容器管理的类不应该去依赖容器所使用的依赖。换句话说,这个类应该是一个简单的 POJO(Plain Ordinary Java Object)能够被单独实例化并且你也能为它提供它所需的依赖。这个问题具体可以表现在:
- 你的类不能绕过反射(例如单元测试的时候)进行实例化,必须通过依赖容器才能实例化,这更像是集成测试
- 你的类和依赖容器强耦合,不能在容器外使用
- 不能使用属性注入的方式构建不可变对象(
final
修饰的变量)Spring 开发团队的建议
Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies.
简单来说,就是
- 强制依赖就用构造器方式
- 可选、可变的依赖就用 setter 注入当然你可以在同一个类中使用这两种方法。构造器注入更适合强制性的注入旨在不变性,Setter 注入更适合可变性的注入。
让我们看看 Spring 这样推荐的理由,首先是基于构造方法注入
❝The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.❞
Spring 团队提倡使用基于构造方法的注入,因为这样一方面可以将依赖注入到一个不可变的变量中 (注:
final
修饰的变量),另一方面也可以保证这些变量的值不会是 null。此外,经过构造方法完成依赖注入的组件 (注:比如各个service
),在被调用时可以保证它们都完全准备好了。与此同时,从代码质量的角度来看,一个巨大的构造方法通常代表着出现了代码异味,这个类可能承担了过多的责任。而对于基于 setter 的注入,他们是这么说的:
❝Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later.❞
基于 setter 的注入,则只应该被用于注入非必需的依赖,同时在类中应该对这个依赖提供一个合理的默认值。如果使用 setter 注入必需的依赖,那么将会有过多的 null 检查充斥在代码中。使用 setter 注入的一个优点是,这个依赖可以很方便的被改变或者重新注入。
关键就是,field注入不建议,依赖对象可能为null而报空指针异常,setter注入适用于可变的属性注入,构造器更全面,可以保证属性不可变,但是如果构造属性过多,维护成本过高。
相关文档:
想用@Autowired注入static静态成员?官方不推荐你却还偏要这么做
Spring Beans and Dependency Injection
2, 基于构造器注入
基于 constructor 注入
将各个必需的依赖全部放在带有注解构造方法的参数中,并在构造方法中完成对应变量的初始化,这种方式,就是基于构造方法的注入。比如:
private final Svc svc;
@Autowired
public HelpService(@Qualifier("svcB") Svc svc) {
this.svc = svc;
}
在 Spring 4.3 及以后的版本中,如果这个类只有一个构造方法,那么这个构造方法上面也可以不写 @Autowired 注解。
If a bean has one constructor, you can omit the @Autowired, as shown in the following example:
@Service
public class DatabaseAccountService implements AccountService {
private final RiskAssessor riskAssessor;
public DatabaseAccountService(RiskAssessor riskAssessor) {
this.riskAssessor = riskAssessor;
}
// ...
}
注意前提条件:只有一个构造方法的时候,@Autowired注解才能省略。
3.将bean注入到static field中(工具类场景)
一般spring中实现的工具类,如果要引入,都需要先注入,但是如果工具类做成static,那么可以避免大量的Autowired工具类的bean。
有两种方式,一种是用setter注入到static属性,一种是用构造方法设置到static属性。
示例:
@Component
public class EncryptUtils {
private static final EncryptService encryptService;
//setter注入
@Autowired
public void setEncryptService(EncryptService encryptService) {
this.encryptService=encryptService;
}
//构造器注入,由于只有一个构造器,所以Autowired省略
public EncryptUtils(EncryptService encryptService) {
this.encryptService=encryptService;
}
}