Spring Bean 与依赖注入
你可以自由地使用任何标准的 Spring 框架技术来定义你的 Bean 及其注入的依赖。为了让一切井井有条,我们通常建议:
- 使用 构造器注入 (Constructor Injection) 来注入依赖。
- 使用
@ComponentScan来发现 Bean。
如果你遵循了前面的代码结构建议(将应用类放在顶层包中),你可以添加不带任何参数的 @ComponentScan,或者直接使用 @SpringBootApplication 注解(它隐式包含了扫描功能)。
你的所有应用组件(@Component, @Service, @Repository, @Controller 等)都会被自动注册为 Spring Bean。
1. 构造器注入 (Constructor Injection)
下面的示例展示了一个 @Service Bean,它通过构造器注入来获取所需的 RiskAssessor Bean:
java
import org.springframework.stereotype.Service;
@Service
public class MyAccountService implements AccountService {
private final RiskAssessor riskAssessor;
public MyAccountService(RiskAssessor riskAssessor) {
this.riskAssessor = riskAssessor;
}
// ...
}kotlin
import org.springframework.stereotype.Service
@Service
class MyAccountService(private val riskAssessor: RiskAssessor) : AccountService如果一个 Bean 有多个构造器,你需要使用 @Autowired 注解来标记你希望 Spring 使用的那一个:
java
import java.io.PrintStream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MyAccountService implements AccountService {
private final RiskAssessor riskAssessor;
private final PrintStream out;
@Autowired
public MyAccountService(RiskAssessor riskAssessor) {
this.riskAssessor = riskAssessor;
this.out = System.out;
}
public MyAccountService(RiskAssessor riskAssessor, PrintStream out) {
this.riskAssessor = riskAssessor;
this.out = out;
}
}kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import java.io.PrintStream
@Service
class MyAccountService : AccountService {
private val riskAssessor: RiskAssessor
private val out: PrintStream
@Autowired
constructor(riskAssessor: RiskAssessor) {
this.riskAssessor = riskAssessor
out = System.out
}
constructor(riskAssessor: RiskAssessor, out: PrintStream) {
this.riskAssessor = riskAssessor
this.out = out
}
}建议
注意到在上面的例子中,通过使用构造器注入,我们可以将 riskAssessor 字段标记为 final,这表明该字段在之后不能被修改,增强了不可变性和线程安全性。
补充教学
1. 为什么不再推荐字段注入 (Field Injection)?
即不再推荐直接在字段上写 @Autowired。构造器注入有以下优势:
- 不可变性:可以配合
final关键字。 - 显式依赖:初始化对象时强制要求传入依赖,避免
NullPointerException。 - 易于测试:不需要通过复杂的反射或 Spring 容器,只需
new一下并传入 Mock 对象即可。 - 解决循环依赖:构造器注入会在启动时立即暴露出循环依赖(而不是运行时)。虽然循环依赖通常是设计问题的体现,但提早发现总比运行时报错好。
2. Spring 4.3+ 的小优化
从 Spring 4.3 开始,如果一个类只有一个构造器,你甚至不需要写 @Autowired 注解,Spring 会自动识别并注入。
3. 组件扫描的范围限制
默认情况下,@ComponentScan 会扫描当前包及其子包。这意味着如果你把类放在了启动类的物理目录层级之上,它们将永远不会被发现。始终检查你的包结构是否符合“包含关系”。