核心现象:同一个类里面,@Bean方法互相调用,Full拿单例,Lite新建对象
1. 两个简单Bean
java
public class ABean {
}
public class BBean {
public ABean aBean;
public BBean(ABean aBean) {
this.aBean = aBean;
}
}
2. Full模式(@Configuration)
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // Full模式,CGLIB代理
public class FullConfig {
@Bean
public ABean aBean() {
System.out.println("Full -> 创建ABean");
return new ABean();
}
@Bean
public BBean bBean() {
// 在配置类内部调用 aBean()
ABean a = aBean();
return new BBean(a);
}
}
3. Lite模式(@Component + @Bean)
java
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component // Lite模式,无CGLIB代理
public class LiteConfig {
@Bean
public ABean aBean() {
System.out.println("Lite -> 创建ABean");
return new ABean();
}
@Bean
public BBean bBean() {
// 普通java方法调用!
ABean a = aBean();
return new BBean(a);
}
}
4. 启动类 & 测试
java
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class BeanDemoApp implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(BeanDemoApp.class, args);
}
@Override
public void run(String... args) throws Exception {
ApplicationContext ctx = BeanDemoApp.this;
// FullConfig
BBean fullB = ctx.getBean("bBean", BBean.class);
ABean fullA = ctx.getBean("aBean", ABean.class);
System.out.println("Full模式:bBean里面的aBean 和容器aBean是否同一个:" + (fullB.aBean == fullA));
// LiteConfig
BBean liteB = ctx.getBean("liteConfig.bBean", BBean.class);
ABean liteA = ctx.getBean("liteConfig.aBean", ABean.class);
System.out.println("Lite模式:bBean里面的aBean 和容器aBean是否同一个:" + (liteB.aBean == liteA));
}
}
控制台输出结果
Full -> 创建ABean
Full模式:bBean里面的aBean 和容器aBean是否同一个:true
Lite -> 创建ABean
Lite -> 创建ABean
Lite模式:bBean里面的aBean 和容器aBean是否同一个:false
解读
-
Full(@Configuration)
bBean()里调用aBean(),不是直接执行方法,而是被代理拦截,从Spring容器返回已存在的单例,只实例化一次。 -
Lite(@Component + @Bean)
没有代理,
bBean()调用aBean()就是普通Java方法执行,new了两次ABean :一次是bBean构造用的,一次是Spring扫描@Bean注册的,两个对象地址不一样。
⚠️ 注意:如果不通过内部方法调用 ,只是单独定义一堆互不引用的
@Bean,Lite模式看起来一切正常,坑藏在内部互相调用场景。
补充:Spring 6 / SpringBoot3 新变化
Spring6 引入 @Configuration(proxyBeanMethods = false)
java
@Configuration(proxyBeanMethods = false)
public class Config {
@Bean
...
}
这个也是Lite模式 !
虽然写了@Configuration,但是关闭代理,内部方法调用同样会新建对象。
现在SpringBoot很多自动配置都用这个,好处:不需要CGLIB,启动更快,代价就是不能内部调用@Bean方法。