Spring - @Configuration 注解中 proxyBeanMethod 属性的作用

概述

@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,从源码注释中我们知道:

  1. 该属性的作用是指定 标注了 @Bean 的方法 在执行生命周期的时候是否应该被代理。比如在代码中直接调用 @Bean 标注的方法 时要返回共享的单例 Bean 实例(关闭代理之后不会返回共享的 Bean 实例)。
  2. 该特性是通过 运行时 CGLIB 子类 实现的方法拦截。该子类有一些限制,比如配置类和它的方法不允许声明为 final 类型。
  3. 默认为 true,即允许 “inter-bean references” 通过配置类内部的直接方法调用,也可以通过外部调用该配置类的 @Bean 标注的方法。如果该配置类是一个特殊的配置类:每一个 @Bean 标注的方法 都是自包含的,并设计为供容器使用的普通工厂方法,可以设置该属性为 false,以避免 CGLIB 子类处理。
  4. 关闭 Bean 方法拦截可以高效的单独处理 @Bean 方法,就像声明在一个 non-@Configuration 类上一样,参考 @Bean Lite Mode。这种行为类似于删除 @Configuration 原型。

分析

针对以上几点,我们做一下测试,以便加深理解:

首先定义两个类 PersonPet

@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,无论调用多少次获取的都是注册到容器中的单例对象。因为默认 proxyBeanMethodstrue,所以我们是使用代理对象调用的 @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 源码中应用的例子

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值