@Configuration 注解相信各位小伙伴经常会用到,但是大家知道吗,这个注解有两种不同的模式,一种叫做 Full 模式,另外一种则叫做 Lite 模式。
准确来说,Full 模式和 Lite 模式其实 Spring 容器在处理 Bean 时的两种不同行为。
这两种不同的模式在使用时候的表现完全不同,今天松哥就来和各位小伙伴捋一捋这两种模式。
1. 概念梳理
首先我们先来看一下 Spring 官方文档中对 Full 模式和 Lite 模式的一个介绍:
图片
截图来自:https://docs.spring.io/spring-framework/reference/core/beans/java/basic-concepts.html
这个文档主要讲了这样几件事情:
官网文档的介绍还是有些抽象,接下来松哥通过具体的案例来和大家演示 Full 模式和 Lite 模式的差别。
2. Full 模式
先看 Full 模式,中文也可以称之为 完整 模式,我们平时使用时,在一个配置类上添加 @Configuration 注解,且不添加任何额外属性,这就是 Full 模式了。
Full 模式最大的特点是会给配置类通过 CGLIB 生成一个代理,所有被 @Bean 注解标记的方法将来都是通过代理方法进行调用。
假设我有如下配置类:
@Configuration
public class JavaConfig {
@Bean
User user() {
return new User();
}
}
现在,我们去 Spring 容器获取这个配置类:
public class JavaDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
JavaConfig config = ctx.getBean(JavaConfig.class);
System.out.println("config.getClass() = " + config.getClass());
}
}
打印结果如下:
图片
大家看到,最终从 Spring 容器中拿到的 JavaConfig 实例并不是原始的 JavaConfig 对象,而是一个被代理的 JavaConfig 对象。
为什么要代理呢?肯定是为了实现某些功能。
大家看下面这个案例:
@Configuration
public class JavaConfig {
@Bean
User user() {
User user = new User();
user.setDog(dog());
return user;
}
@Bean
Dog dog() {
return new Dog();
}
}
在 Full 模式下,在 user() 方法中调用 dog() 方法的时候,调用的是一个代理对象的 dog 方法,在这个代理对象的 dog 方法中,会首先去检查 Spring 容器中是否存在 Dog 对象,如果存在,则直接使用 Spring 容器中的 dog 对象,就不会真正去执行 dog 方法而获取到一个新的 dog 对象了,如果 Spring 容器中不存在 dog 对象,才会创建新的 dog 对象出来。
一言以蔽之,在 Full 模式下,user 中的 dog 对象和 dog 方法注册到 Spring 容器的 dog 对象是同一个。
在 Full 模式下,由于要给当前类生成代理,然后去代理 @Bean 注解标记的方法,因此,这些 @Bean 注解标记的方法不能是 final 或者 private 类型的,因为 final 或者 private 类型的方法无法被重写,也就没法生成代理对象,如果添加了 final 或者 private 修饰符,那么会抛出如下异常:
图片
3. Lite 模式
再来看 Lite 模式,这种模式可以认为是一种精简模式。
怎么开启呢?我们可以去除配置类上的 @Configuration 注解,或者去除之后添加 @Component 注解,又或者使用 @ComponentScan、@ImportResource、@Import 等注解标记类,那么最终都是 Lite 模式:
@Component
public class JavaConfig {
@Bean
final User user() {
User user = new User();
user.setDog(dog());
return user;
}
@Bean
Dog dog() {
return new Dog();
}
}
此时就是 Lite 模式,现在我们去 Spring 容器中获取这个配置类:
public class JavaDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
JavaConfig config = ctx.getBean(JavaConfig.class);
System.out.println("config.getClass() = " + config.getClass());
}
}
最终打印结果如下:
图片
大家看到,我们从 Spring 容器中拿到的就是原始的对象,而不是一个被代理过的对象。因此:
针对第二点,如果想要确保 user 中的 dog 和 Spring 容器中的 dog 是同一个,那么可以通过参数将所需要的对象注入进来,类似下面这样:
@Component
public class JavaConfig {
@Bean
final User user(Dog dog) {
User user = new User();
user.setDog(dog);
return user;
}
@Bean
Dog dog() {
return new Dog();
}
}
当 Spring 容器调用 user 方法初始化 User 对象时,发现该方法还有参数,因此会去容器中查找这个参数,找到了直接使用。
另外,我们也可以在类上添加 @Configuration 注解,但是通过修改属性值来启用 Lite 模式:
@Configuration(proxyBeanMethods = false)
public class JavaConfig {
@Bean
final User user(Dog dog) {
User user = new User();
user.setDog(dog);
return user;
}
@Bean
Dog dog() {
return new Dog();
}
}
如果设置了 proxyBeanMethods 属性为 false,那么也就是 Lite 模式了,其实我们从属性名称上也能看出来端倪:是否代理 @Bean 注解标记的方法。
4. 小结
总结一下:
日常开发中,我们使用较多的是 Full 模式。