
本文详解 Spring 配置类(@Configuration)中因字段注入引发的“自引用”循环依赖问题,阐明为何 @Bean 方法应避免非静态字段注入,并介绍 static 声明与 @Lazy 延迟代理两种核心解决策略。
本文详解 spring 配置类(@configuration)中因字段注入引发的“自引用”循环依赖问题,阐明为何 `@bean` 方法应避免非静态字段注入,并介绍 `static` 声明与 `@lazy` 延迟代理两种核心解决策略。
在 Spring 的 @Configuration 类中,“自引用”(self-reference)并非指 @Bean 方法之间通过参数或方法调用相互依赖(如 teacher(Course course) 或 course() 被其他 @Bean 方法显式调用),而是特指 配置类自身通过字段(或构造器参数)直接依赖由本类定义的 @Bean 所产生的 Bean 实例。这种依赖会触发 Spring 容器初始化时的循环依赖困境。
为什么字段注入会导致自引用?
考虑以下错误示例:
@Configuration
public class JavaConfig {
@Autowired
private Course course; // ❌ 危险:配置类字段直接依赖本类定义的 bean
@Bean
Course course() {
return new Course();
}
}
表面看逻辑清晰,但 Spring 在实例化 JavaConfig 时需完成两件事:
- 创建
JavaConfig实例; - 注入其
@Autowired字段(此处为course)。
而注入 course 字段的前提是:已存在一个 Course Bean —— 它由 course() 方法提供。但该方法是非静态实例方法,必须先拥有 JavaConfig 实例才能调用。这就形成了「要创建 JavaConfig,需先有 Course;要有 Course,需先有 JavaConfig」的闭环,无法打破。
同理,构造器注入也暴露此问题:
@Configuration
public class JavaConfig {
private final Course course;
public JavaConfig(Course course) { // 构造器参数依赖本类 @Bean
this.course = course;
}
@Bean
Course course() {
return new Course();
}
}
Spring 无法在构造 JavaConfig 时提供 Course,因为 course() 尚未可调用(实例未就绪)。
✅ 正确解法一:声明 @Bean 方法为 static
将 @Bean 方法设为 static,彻底解除对配置类实例的依赖:
@Configuration
public class JavaConfig {
@Autowired
private Course course; // ✅ 现在合法:course() 是 static,无需实例即可调用
@Bean
static Course course() { // ✔️ 关键:static 方法不依赖 this
return new Course();
}
}
此时 Spring 可在不创建 JavaConfig 实例的前提下,直接通过反射调用 JavaConfig.course() 获取 Course 实例,从而安全完成字段注入。注意:static @Bean 方法内不可访问非静态成员(如 this.xxx 或普通字段),因其无实例上下文。
✅ 正确解法二:使用 @Lazy 实现延迟代理注入
若必须保留非静态 @Bean 方法且需字段注入,可在依赖字段上添加 @Lazy:
@Configuration
public class JavaConfig {
@Lazy // ✔️ 关键:注入一个 CGLIB 代理,实际调用时才触发 course() 执行
@Autowired
private Course course;
@Bean
Course course() {
System.out.println("course() is invoked"); // 仅首次调用 course.toString() 等方法时打印
return new Course();
}
}
@Lazy 使 Spring 注入一个轻量级代理对象(而非真实 Course 实例)。只有当代码首次调用该代理上的非 final、非 private 方法(如 course.getName())时,Spring 才真正调用 course() 方法并缓存结果。这打破了初始化时的循环链。
⚠️ 注意事项:
-
@Lazy不适用于final字段或private字段(CGLIB 无法代理); - 若
Course类无公共方法(仅有字段),代理将无效,应优先选用static方案; -
@Lazy作用于字段/构造器参数,不应用于@Bean方法签名中的参数(Spring 默认已对@Bean参数执行懒解析)。
总结:何时用哪种方案?
| 场景 | 推荐方案 | 原因 |
|---|---|---|
@Bean 方法不依赖配置类状态(如无 this.xxx 引用) |
✅ static @Bean
|
最高效、无代理开销、语义清晰、线程安全 |
@Bean 方法需访问配置类非静态成员(如 @Value、@ConfigurationProperties 绑定的字段) |
✅ @Lazy 字段注入 |
唯一可行方案,但需确保被代理类具备可代理性 |
@Bean 方法间参数依赖(如 teacher(Course course)) |
⚠️ 无需特殊处理 | Spring 原生支持方法参数注入,属于正向依赖,非自引用 |
牢记:“as opposed to an autowired field in the configuration class” 的本质,是警示开发者——不要让配置类自身成为自己所产 Bean 的消费者;若必须,则务必通过 static 或 @Lazy 主动解耦生命周期。











