Skip to content

Spring Bean 与依赖注入

你可以自由地使用任何标准的 Spring 框架技术来定义你的 Bean 及其注入的依赖。为了让一切井井有条,我们通常建议:

  1. 使用 构造器注入 (Constructor Injection) 来注入依赖。
  2. 使用 @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 会扫描当前包及其子包。这意味着如果你把类放在了启动类的物理目录层级之上,它们将永远不会被发现。始终检查你的包结构是否符合“包含关系”。

Based on Spring Framework.