@Configuration 注解是 Spring 提供的以 Java 代码方式 配置 IoC 容器 的一种方式。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
boolean proxyBeanMethods() default true;
}
在 Spring 5.2 版本之后,提供了一个属性 proxyBeanMethods,从源码注释中我们知道:
- 该属性的作用是指定 标注了 @Bean 的方法 在执行生命周期的时候是否应该被代理。比如在代码中直接调用 @Bean 标注的方法 时要返回共享的单例 Bean 实例(关闭代理之后不会返回共享的 Bean 实例)。
- 该特性是通过 运行时 CGLIB 子类 实现的方法拦截。该子类有一些限制,比如配置类和它的方法不允许声明为 final 类型。
- 默认为 true,即允许 “inter-bean references” 通过配置类内部的直接方法调用,也可以通过外部调用该配置类的 @Bean 标注的方法。如果该配置类是一个特殊的配置类:每一个 @Bean 标注的方法 都是自包含的,并设计为供容器使用的普通工厂方法,可以设置该属性为 false,以避免 CGLIB 子类处理。
- 关闭 Bean 方法拦截可以高效的单独处理 @Bean 方法,就像声明在一个 non-@Configuration 类上一样,参考 @Bean Lite Mode。这种行为类似于删除 @Configuration 原型。
针对以上几点,我们做一下测试,以便加深理解:
首先定义两个类 Person、Pet:
@Data
@AllArgsConstructor
public class Person {
private String name;
private Integer age;
}
@Data
@AllArgsConstructor
public class Pet {
private String name;
}
定义配置类,定义 Bean,并加入到容器中:
@Configuration("appConfig")
public class MainConfig3 {
@Bean
public Person person() {
return new Person("Nikang", 46);
}
@Bean
public Pet pet() {
return new Pet("tom");
}
}
获取 Bean:
@Test
void testProxyBeanMethod() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class);
printBeans(applicationContext);
// 注册到容器中的 Bean 默认都是单例的
Person person = applicationContext.getBean(Person.class);
Person person1 = applicationContext.getBean(Person.class);
System.out.println(person == person1); // true
}
针对第一点和第三点
// proxyBeanMethods 属性默认为 true boolean proxyBeanMethods() default true;
当使用代码调用 @Bean 注解的方法获取 Bean 时:
MainConfig3 config = applicationContext.getBean(MainConfig3.class); Person person = config.person(); Person person1 = config.person(); System.out.println(person == person1); // true
结果可知:外部获取 Bean,无论调用多少次获取的都是注册到容器中的单例对象。因为默认 proxyBeanMethods 为 true,所以我们是使用代理对象调用的 @Bean 方法,首先会去 IoC 容器 中查询是否有这个组件,有则返回,无则创建。
当设置 proxyBeanMethods 值为 false 时:
@Configuration(proxyBeanMethods = false)
再判断调用 @Bean 方法 获取的 Bean 是否相等:
但是通过 applicationContext 获取的 Bean 仍然是单例且相等的。并且获取到的配置类对象也不是经 CGLIB 代理过的对象了,而是对象本身
MainConfig3 config = applicationContext.getBean(MainConfig3.class); System.out.println(config); // com.keke.config.MainConfig3@43599640 // 外部调用,如果 proxyBeanMethods 设置为 false,则不会去 IoC 容器中找,即是否要代理 Bean 方法 Person person = config.person(); Person person1 = config.person(); System.out.println(person == person1); // false // 直接从容器中获取 Person person2 = applicationContext.getBean(Person.class); Person person3 = applicationContext.getBean(Person.class); System.out.println(person2 == person3); // true针对第二点
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig3.class); MainConfig3 config = applicationContext.getBean(MainConfig3.class); System.out.println(config); # 输出 com.keke.config.MainConfig3$$EnhancerBySpringCGLIB$$9f4a4070@598bd2ba
可以看到,默认设置 proxyBeanMethods 属性为 true 的情况下,配置类是一个由 CGLIB 代理的子类
针对第四点如果组件间有依赖的话:
@Configuration(value = "appConfig", proxyBeanMethods = false)
public class MainConfig3 {
@Bean
public Person person() {
Person nikang = new Person("Nikang", 46);
// 依赖 pet()
nikang.setPet(pet());
return nikang;
}
@Bean
public Pet pet() {
return new Pet("tom");
}
}
当我想要使用上述代码时,IDEA 提示如下(但仍然可以编译、运行通过):
Method annotated with @Bean is called directly in a @Configuration where proxyBeanMethods set to false. Set proxyBeanMethods to true or use dependency injection.
需要我们设置 proxyBeanMethods 为 true,或者使用 依赖注入,只有这样,才能保证我们 set 的 pet() 方法和 @Bean pet() 方法是同一个 IoC 容器中的组件:
// 获取容器中的 Person 组件 Person person4 = applicationContext.getBean(Person.class); // 获取容器中的 Pet 组件 Pet pet = applicationContext.getBean(Pet.class); // 比较 Person 组件中 set 的 pet() 方法与 pet 组件 System.out.println(person4.getPet() == pet); // true
显然,当设置 proxyBeanMethods 为 true 时,当使用到组件时,始终都会去 IoC 容器中获取组件,以保证单例
总结- @Bean Full Mode:proxyBeanMethods = true
- @Bean Lite Mode:proxyBeanMethods = false,不需要去 IoC 容器中查找
- 当配置类组件之间无依赖关系时,用 Lite 模式 可以加速容器启动过程,减少判断
- 当配置类组件之间有依赖关系时,@Bean 方法 会被调用得到之前单实例组件,使用 Full 模式
TODO:补充 SpringBoot 源码中应用的例子
暑期编程PK赛 得CSDN机械键盘等精美礼品!


