Spring Bean Full vs Lite

igxiaoshan Lv5

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
// =================== MyConfig配置类 ===================
@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);
}
}

// 测试结果: 输出 myConfig
com.igsshan.springbootbasic.configs.MyConfig$$EnhancerBySpringCGLIB$$3cc6a4ab@127d7908
// 可以看出,最终输入的 MyConfig 实例,并不是原始的 MyConfig 对象,而是一个被代理的 MyConfig 对象
  • 为什么要代理呢?

看下面的案例,所实现的功能

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
// =================== MyConfig配置类 ===================
@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
// =================== MyConfig配置类 ===================

//@Configuration
@Component
public class MyConfig {

@Bean
public User user01() {
User user = new User("小张", 19);
// user.setPet(tomCat());
return user;
}

@Bean
final public User user01() {
User user = new User("小张", 19);
// user.setPet(tomCat());
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));
}
}

// 输出结果: 输出 myConfig
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 修饰