Spring Bean初始化机制解析:配置类中@Bean方法调用原理
问题:Spring配置类中@Bean方法互相调用为何只生成一个实例?
我无法理解以下代码的运行现象:原本预期日志中会出现InstanceID=2的Bean,但实际两个类的InstanceID均为1。想了解Spring是如何实现这种机制的,尤其是当配置类中一个带@Bean注解的方法调用另一个@Bean方法时,具体发生了什么?
代码片段
package com.example.test.simple; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; @SpringBootTest @Import(TestConfigOverride.Config.class) public class TestConfigOverride { private static final Logger L = LoggerFactory.getLogger(TestConfigOverride.class); public static class TestService { private static final AtomicInteger instanceIndex = new AtomicInteger(1); private final int instanceId; public TestService() { instanceId = instanceIndex.getAndIncrement(); } public int getInstanceId() { return instanceId; } } public static class TestWrapperService { private TestService testService; public TestWrapperService(TestService testService) { this.testService = testService; } public int getInstanceId() { return testService.getInstanceId(); } } @TestConfiguration public static class Config { @Bean public TestWrapperService testWrapperService() { return new TestWrapperService(testService()); } @Bean public TestService testService() { return new TestService(); } } @Autowired ApplicationContext context; @Test public void test() { var testServiceBeans = context.getBeansOfType(TestService.class); testServiceBeans.forEach((n, s) -> L.debug("Name=" + n + ", InstanceID=" + s.getInstanceId())); var testWrapperServiceBeans = context.getBeansOfType(TestWrapperService.class); testWrapperServiceBeans.forEach((n, s) -> L.debug("Name=" + n + ", InstanceID=" + s.getInstanceId())); } }
输出结果
DEBUG 13052 --- [ main] c.e.test.simple.TestConfigOverride : Name=testService, InstanceID=1 DEBUG 13052 --- [ main] c.e.test.simple.TestConfigOverride : Name=testWrapperService, InstanceID=1
原因解析
核心在于Spring对标注了@Configuration(包括@TestConfiguration,它是@Configuration的派生注解)的类会生成CGLIB代理,这个代理会拦截配置类内部的@Bean方法调用,确保始终返回Spring容器中已存在的Bean实例(默认单例模式),而不是每次调用都执行原始方法创建新对象。
具体到你的代码:
- 当Spring初始化容器时,会先处理
Config类(因为它是@TestConfiguration),生成代理类。 - 当创建
testWrapperServiceBean时,调用testService()方法——此时代理会先检查容器中是否已经存在testService类型的Bean:- 如果不存在,就执行原始的
testService()方法创建实例(InstanceID=1),并将其放入容器; - 如果已经存在,直接返回容器中的实例。
- 如果不存在,就执行原始的
- 后续Spring处理
testServiceBean的定义时,发现容器中已经有这个实例了,就不会再执行原始方法创建新对象。
所以最终不管是容器中的testService Bean,还是testWrapperService依赖的testService,都是同一个实例,InstanceID自然都是1。
验证反向场景
如果想让@Bean方法调用生成新实例,可以做以下修改:
- 把
@TestConfiguration注解去掉,此时Config类只是普通类,Spring不会生成代理,调用testService()会直接执行原始方法,生成两个不同实例; - 或者使用
@Configuration(proxyBeanMethods = false)(Spring 5.2及以上版本),关闭配置类的代理功能,同样会让@Bean方法调用直接执行原始逻辑。
内容的提问来源于stack exchange,提问作者Ajava Dev
相关产品推荐
相关产品推荐

