概述
@Configuration
注解是Spring
提供的以Java 代码方式
配置IoC 容器
的一种方式。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
/**
* Explicitly specify the name of the Spring bean definition associated with the
* {@code @Configuration} class. If left unspecified (the common case), a bean
* name will be automatically generated.
* <p>The custom name applies only if the {@code @Configuration} class is picked
* up via component scanning or supplied directly to an
* {@link AnnotationConfigApplicationContext}. If the {@code @Configuration} class
* is registered as a traditional XML bean definition, the name/id of the bean
* element will take precedence.
* @return the explicit component name, if any (or empty String otherwise)
* @see AnnotationBeanNameGenerator
*/
@AliasFor(annotation = Component.class)
String value() default "";
/**
* Specify whether {@code @Bean} methods should get proxied in order to enforce
* bean lifecycle behavior, e.g. to return shared singleton bean instances even
* in case of direct {@code @Bean} method calls in user code. This feature
* requires method interception, implemented through a runtime-generated CGLIB
* subclass which comes with limitations such as the configuration class and
* its methods not being allowed to declare {@code final}.
* <p>The default is {@code true}, allowing for 'inter-bean references' via direct
* method calls within the configuration class as well as for external calls to
* this configuration's {@code @Bean} methods, e.g. from another configuration class.
* If this is not needed since each of this particular configuration's {@code @Bean}
* methods is self-contained and designed as a plain factory method for container use,
* switch this flag to {@code false} in order to avoid CGLIB subclass processing.
* <p>Turning off bean method interception effectively processes {@code @Bean}
* methods individually like when declared on non-{@code @Configuration} classes,
* a.k.a. "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore
* behaviorally equivalent to removing the {@code @Configuration} stereotype.
* @since 5.2
*/
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 源码中应用的例子