核心现象:同一个类里面,@Bean方法互相调用,Full拿单例,Lite新建对象
1. 两个简单Bean
publicclassABean{}publicclassBBean{publicABeanaBean;publicBBean(ABeanaBean){this.aBean=aBean;}}2. Full模式(@Configuration)
importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@Configuration// Full模式,CGLIB代理publicclassFullConfig{@BeanpublicABeanaBean(){System.out.println("Full -> 创建ABean");returnnewABean();}@BeanpublicBBeanbBean(){// 在配置类内部调用 aBean()ABeana=aBean();returnnewBBean(a);}}3. Lite模式(@Component + @Bean)
importorg.springframework.context.annotation.Bean;importorg.springframework.stereotype.Component;@Component// Lite模式,无CGLIB代理publicclassLiteConfig{@BeanpublicABeanaBean(){System.out.println("Lite -> 创建ABean");returnnewABean();}@BeanpublicBBeanbBean(){// 普通java方法调用!ABeana=aBean();returnnewBBean(a);}}4. 启动类 & 测试
importorg.springframework.boot.CommandLineRunner;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.context.ApplicationContext;@SpringBootApplicationpublicclassBeanDemoAppimplementsCommandLineRunner{publicstaticvoidmain(String[]args){SpringApplication.run(BeanDemoApp.class,args);}@Overridepublicvoidrun(String...args)throwsException{ApplicationContextctx=BeanDemoApp.this;// FullConfigBBeanfullB=ctx.getBean("bBean",BBean.class);ABeanfullA=ctx.getBean("aBean",ABean.class);System.out.println("Full模式:bBean里面的aBean 和容器aBean是否同一个:"+(fullB.aBean==fullA));// LiteConfigBBeanliteB=ctx.getBean("liteConfig.bBean",BBean.class);ABeanliteA=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)
@Configuration(proxyBeanMethods=false)publicclassConfig{@Bean...}这个也是Lite模式!
虽然写了@Configuration,但是关闭代理,内部方法调用同样会新建对象。
现在SpringBoot很多自动配置都用这个,好处:不需要CGLIB,启动更快,代价就是不能内部调用@Bean方法。