Spring Bean 的 Full 和 Lite模式
官网文献: https://docs.spring.io/spring-framework/reference/core/beans/java/basic-concepts.html
Spring中都是bean,使用不同的注解标记,常见标记配置类bean的注解有
@Configuration, @Component, @ComponentScan, @Import, @ImportResource
Full vs Lite
根据文档可以了解到
在一个方法上标记@Bean注解,并将该方法的返回值暴漏给Spring容器,在这种场景下,@Bean注解实际上就是一种通用的工厂方法机制
当一个添加了@Bean注解的方法位于一个没有添加@Configuration注解的类里面时,那么这个添加了@Bean注解的方法在处理时就会按照lite模式来处理
当一个Bean被声明在添加了@Component注解的类中,那么会按照lite模式来处理
当一个Bean被声明在一个普通的类中时,会按照lite模式来处理
在lite模式下,@Bean注解标记的方法最终不会被CGLIB进行代理,就是一个普通的工厂方法.因此,在@Bean标记的方法中,不能调用其他@Bean注解标记的方法.如果有需要,可以通过方法参数注入所需要的Bean
由于lite模式下并不会使用CGLIB,因此@Bean标记的方法可以是final类型的
在大多场景下,在一个@Configuration注解标记的类中,使用@Bean注解向 Spring 容器注册一个 Bean,都是full模式
简单来说
1 2 3 4 5 6 7 8 1. 类上标注有 @Component 注解 (@Service,@Controller注解上都有此方法) 2. 类上标注有 @ComponentScan 注解 3. 类上标注有 @Import 注解 4. 类上标注有 @ImportResource 注解 5. 若类上没有任何注解,但是类内方法存在 @Bean 注解 以上,前提都是没有使用 @Configuration 注解;(在 Spring 5.2 之后,新增了 @Configuration 属性 proxyBeanMethods; 当 proxyBeanMethods(注意: 默认值为 TRUE) 值为 FLASE 时,也是属于 lite 模式的配置类 ) 上面列举的都可以称为配置类,属于 lite 模式的配置类
Full 模式
full模式, 在一个配置类上添加@Configuration注解,且不添加任何额外属性,就是full模式
full模式最大的特点就是会给配置类通过CGLIB生成一个代理,所有被@Bean注解标记的方法都是通过代理方法进行调用
代码演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Configuration public class MyConfig { @Bean public User user01 () { return new User (); } } @SpringBootApplication public class HelloWorldApplication { public static void main (String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(HelloWorldApplication.class, args); MyConfig myConfig = applicationContext.getBean(MyConfig.class); System.out.println(myConfig); } } com.igsshan.springbootbasic.configs.MyConfig$$EnhancerBySpringCGLIB$$3cc6a4ab@127d7908
看下面的案例,所实现的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 @Configuration public class MyConfig { @Bean public User user01 () { User user = new User (); user.setPet(tomCat()); return user; } @Bean public Pet tomCat () { return new Pet ("tom" ); } } @SpringBootApplication public class HelloWorldApplication { public static void main (String[] args) { long start = System.currentTimeMillis(); ConfigurableApplicationContext applicationContext = SpringApplication.run(HelloWorldApplication.class, args); MyConfig myConfig = applicationContext.getBean(MyConfig.class); System.out.println(myConfig); System.out.println("======================================================" ); User user01 = applicationContext.getBean("user01" , User.class); Pet tom = applicationContext.getBean("tom" , Pet.class); System.out.println("用户的宠物:" + (user01.getPet() == tom)); System.out.println("======================================================" ); Pet tom01 = applicationContext.getBean("tom" , Pet.class); Pet tom02 = applicationContext.getBean("tom" , Pet.class); System.out.println(tom01); System.out.println("组件:" + (tom01 == tom02)); System.out.println("======================================================" ); User user = myConfig.user01(); User user1 = myConfig.user01(); System.out.println(user); System.out.println("配置类user01方法:" + (user == user1)); System.out.println("======================================================" ); long end = System.currentTimeMillis(); System.out.println("测试时间: " + (end - start)); } } com.igsshan.springbootbasic.configs.MyConfig$$EnhancerBySpringCGLIB$$676c133b@3249a1ce ====================================================== 用户的宠物:true ====================================================== com.igsshan.springbootbasic.model.Pet@4dd94a58 组件:true ====================================================== com.igsshan.springbootbasic.model.User@2f4919b0 配置类user01方法:true ====================================================== 测试时间: 1388
结论
1 2 3 4 5 1. full 模式下,user中pet对象和tomCat方法注入注入到 Spring 容器中的对象是同一个 2. full 模式下,user对象调用tomCat()方法的时候,调用的是一个代理对象;在这个代理对象的tomCat()方法中,会首先去检查 Spring 容器中是否存在 Pet 对象;如果存在,则直接用 Spring 容器中的pet对象,就不会真正执行tomCat()方法而获取一个新的Pet对象了;如果 Spring 容器中不存在 Pet对象,才会创建新的 Pet对象 3. full 模式下,由于要给当前类生成代理,然后去代理 @Bean 注解标记的方法;因此,这些 @Bean 注解标记的方法不能是 final 或者 private 类型的,因为 final 或者 private 类型的方法无法被重写,也就没法生成代理对象,如果添加了 final 或者 private 修饰符,会抛出异常( org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: @Bean method 'tomCat' must not be private or final; change the method's modifiers to continue Offending resource: class path resource [com/igsshan/springbootbasic/configs/MyConfig.class] )
Lite 模式
如何开启 lite模式
除配置类上的 @Configuration 注解,或者去除之后添加 @Component 注解,或者使用@ComponentScan、@ImportResource、@Import 等注解标记类,那么最终都是 Lite 模式
代码演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 @Component public class MyConfig { @Bean public User user01 () { User user = new User ("小张" , 19 ); return user; } @Bean final public User user01 () { User user = new User ("小张" , 19 ); return user; } @Bean("tom") public final Pet tomCat () { return new Pet ("tom" ); } } @SpringBootApplication public class HelloWorldApplication { public static void main (String[] args) { long start = System.currentTimeMillis(); ConfigurableApplicationContext applicationContext = SpringApplication.run(HelloWorldApplication.class, args); MyConfig myConfig = applicationContext.getBean(MyConfig.class); System.out.println(myConfig); System.out.println("======================================================" ); User user01 = applicationContext.getBean("user01" , User.class); Pet tom = applicationContext.getBean("tom" , Pet.class); System.out.println("用户的宠物:" + (user01.getPet() == tom)); System.out.println("======================================================" ); Pet tom01 = applicationContext.getBean("tom" , Pet.class); Pet tom02 = applicationContext.getBean("tom" , Pet.class); System.out.println(tom01); System.out.println("组件:" + (tom01 == tom02)); System.out.println("======================================================" ); User user = myConfig.user01(); User user1 = myConfig.user01(); System.out.println(user); System.out.println("配置类user01方法:" + (user == user1)); System.out.println("======================================================" ); long end = System.currentTimeMillis(); System.out.println("测试时间: " + (end - start)); } } com.igsshan.springbootbasic.configs.MyConfig@f6de586 ====================================================== 用户的宠物:false ====================================================== com.igsshan.springbootbasic.model.Pet@5f2bd6d9 组件:true ====================================================== com.igsshan.springbootbasic.model.User@7c847072 配置类user01方法:false ====================================================== 测试时间: 1281
结论
1 2 3 1. lite 模式下,配置类中的方法就是普通方法,可以是final类型,也可以是private 2. lite 模式下,不需要通过 CGLIB 生成动态代理类,所以启动速度会快一些 3. lite 模式下,一个 @Bean 方法调用另外一个 @Bean 方法,会导致同一个 Bean 被初始化两次
总结 full 模式
1 2 3 4 1. 配置类会被 CGLIB 增强(生成代理对象),放进 IOC 容器内的是代理 2. 对于内部类是没有限制的: 可以是 full 模式或者 lite 模式 3. 配置类内部可以通过方法调用来处理依赖,并且能够保证是同一个实例都指向 IOC 内的那个单例 4. @Bean 方法不能被 private 或者 final 等进行修饰,因为代理类需要重写这个方法
lite 模式
1 2 3 4 1. 配置类本身不会被 CGLIB 增强,放进 IOC 容器内的就是类本身 2. 对于内部类是没有限制的: 可以是 full 模式或者 lite 模式 3. 配置类内部不能通过方法调用来处理依赖,否则每次生成的都是一个新的实例并非 IOC 容器内的单例 4. 配置类就是一个普通类,所以 @Bean 方法可以使用 private 或者 final 修饰