You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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实例(默认单例模式),而不是每次调用都执行原始方法创建新对象。

具体到你的代码:

  1. 当Spring初始化容器时,会先处理Config类(因为它是@TestConfiguration),生成代理类。
  2. 当创建testWrapperService Bean时,调用testService()方法——此时代理会先检查容器中是否已经存在testService类型的Bean:
    • 如果不存在,就执行原始的testService()方法创建实例(InstanceID=1),并将其放入容器;
    • 如果已经存在,直接返回容器中的实例。
  3. 后续Spring处理testService Bean的定义时,发现容器中已经有这个实例了,就不会再执行原始方法创建新对象。

所以最终不管是容器中的testService Bean,还是testWrapperService依赖的testService,都是同一个实例,InstanceID自然都是1。

验证反向场景

如果想让@Bean方法调用生成新实例,可以做以下修改:

  • 把@TestConfiguration注解去掉,此时Config类只是普通类,Spring不会生成代理,调用testService()会直接执行原始方法,生成两个不同实例;
  • 或者使用@Configuration(proxyBeanMethods = false)(Spring 5.2及以上版本),关闭配置类的代理功能,同样会让@Bean方法调用直接执行原始逻辑。

内容的提问来源于stack exchange,提问作者Ajava Dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 03:33:17