刨析Spring的@Configuration注解属性proxyBeanMethods原理

前言

Spring框架中有一个出现频次很高的注解@Configuration,特别是在SpringBoot的spring-boot-autoconfigure包中,可以发现每个配置类基本都会加上@Configuration(proxyBeanMethods=false)注解(注:Spring 5.2+),其中proxyBeanMethods属性起到什么作用呢?今天我们就通过源码探究它的底层原理

使用

常见用法

//属性proxyBeanMethods默认为true
@Configuration
public class AppConfig {
    @Bean
    public MyBean myBean() {
        // instantiate, configure and return bean ...
    }
}

SpringBoot Autoconfig中的用法

@Configuration(proxyBeanMethods = false)
public class TaskExecutionAutoConfiguration {
	@Lazy
	@Bean(name = APPLICATION_TASK_EXECUTOR_BEAN_NAME)
	@ConditionalOnMissingBean(Executor.class)
	public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
		return builder.build();
	}
}

通过案例先看一下运行效果:

proxyBeanMethods = false

@Configuration(proxyBeanMethods = false)
public class AppConfig {
	@Bean
    public Role role() {
        System.out.println("初始化Role对象!");
        return new Role();
    }
    
    @Bean
    public User user() {
        Role role = this.role();
        return new User();
    }
}

输出结果
初始化Role对象!
初始化Role对象!

proxyBeanMethods = true

@Configuration(proxyBeanMethods = true)
public class AppConfig {
	@Bean
    public Role role() {
        System.out.println("初始化Role对象!");
        return new Role();
    }
    
    @Bean
    public User user() {
        Role role = this.role();
        return new User();
    }
}

输出结果
初始化Role对象!

源码分析

Spring启动时会扫描所有带@Component和@Configuration注解的类,并将这些类定义为BeanDefinition并且放到beanDefinitionMap集合中,然后Spring会针对这些BeanDefinition做一些BeanFactory的后置处理,其中有一个类ConfigurationClassPostProcessor,如果类上配置了@Configuration且属性proxyBeanMethods=true则使用Spring CGLIB生成一个对应的配置增强类,当调用@Bean注解上的方法进行实例化对象时会优先在spring容器里查找,如果存在就直接返回,否则就会走通用的Spring Bean的初始化流程

源码如下:

类ConfigurationClassPostProcessor

入口方法:postProcessBeanDefinitionRegistry()
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
	//省略...
	processConfigBeanDefinitions(registry);
}
方法:processConfigBeanDefinitions

遍历beanDefinitionNames集合,判断这些类是否配置了@Configuration注解

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	//遍历
	for (String beanName : candidateNames) {
		//判断beanDef定义的类上是否有@Configuration注解
		if(ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
			configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
		}
	}
}

进入ConfigurationClassUtils类,查看checkConfigurationClassCandidate方法

类ConfigurationClassUtils

方法:checkConfigurationClassCandidate()

该方法会判断bean类上是否配置了@Configuration注解,如果配置了且proxyBeanMethods属性为true,则给该类对应的beanDefinition设置一个Attribute值(CONFIGURATION_CLASS_ATTRIBUTE->full),否则Attribute值为(CONFIGURATION_CLASS_ATTRIBUTE->lite),这是@Configuration的两种配置模式即:FULL和LITE

注意:后面的步骤会使用到该值

public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
	//...
	Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
	//如果proxyBeanMethods属性值为True,则设值为full
	if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
		beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
	}
	//如果proxyBeanMethods属性值为false,则庙会为lite
	else if (config != null || isConfigurationCandidate(metadata)) {
		beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
	}
	//...
}

类ConfigurationClassPostProcessor

入口方法:postProcessBeanFactory()

通过用CGLIB增强的子类替换配置类,调用enhanceConfigurationClasses方法进行增强

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	//针对配置类进行增强,生成cglib代理对象
	enhanceConfigurationClasses(beanFactory);
}
方法:enhanceConfigurationClasses()

主要关注beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE)这一段代码,上一步已经对该beanDefinition对象设置了attribute值,如果该值为lite,则不会生成代理对象,如果该值为full(即:类上配置了@Congiguration且proxyBeanMethods=true)则生成cglib对象,格式为:

com.showcase.config.AppConfig$$EnhancerBySpringCGLIB$$1678297f

代码如下:

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
	Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>()
	//循环所有的BeanDefinition对象,这里我们只看AppConfig(例子)
	for (String beanName : beanFactory.getBeanDefinitionNames()) {
		BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
		Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
		//...
		//如果配置了Configuration且proxyBeanMethods=true,将生成cglib对象,否则返回原生对象
		if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
			configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef)
		}
	}
	
	if (configBeanDefs.isEmpty()) {
		return;
	}
	
	ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
	for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
		//...
		Class<?> configClass = beanDef.getBeanClass();
		//针对类AppConfig生成enhanced增强类
		Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
		if (configClass != enhancedClass) {
			//设置AppConfig对象的类型为AppConfig$$EnhancerBySpringCGLIB
			beanDef.setBeanClass(enhancedClass);
		}
	}
}

通过代码我们可以看到,代理增强类是通过ConfigurationClassEnhancer类的enhance()方法来生成的,继续往下看

类ConfigurationClassEnhancer

方法:enhance()
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
	//...省略
	Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
	return enhancedClass;
}
方法:createClass()
private Class<?> createClass(Enhancer enhancer) {
	Class<?> subclass = enhancer.createClass();
	// Registering callbacks statically (as opposed to thread-local)
	// is critical for usage in an OSGi environment (SPR-5932)...
	Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
	return subclass;
}

基中CALLBACKS变量定义了具体的interceptor对象,该变量声明在类ConfigurationClassEnhancer中,如下:

private static final Callback[] CALLBACKS = new Callback[] {
	new BeanMethodInterceptor(),
	new BeanFactoryAwareMethodInterceptor(),
	NoOp.INSTANCE
};

我们关注的BeanMethodInterceptor就是文章开始提到的问题关键点了,通过这个interceptor类就可以实现从beanFacory中查找是否已经存在Role对象了,如果存在则直接返回,否则重新创建

类BeanMethodInterceptor

@Override
@Nullable
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,MethodProxy cglibMethodProxy) throws Throwable {
	//①方法
	if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
		return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
	}
	
	//②方法
	return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}

①、判断当前调用的方法和主方法是不是同一个,有点绕!以代码为例

@Bean
public Role role() {
	System.out.println("初始化Role对象!");
	return new Role();
}
    
@Bean
public User user() {
    Role role = this.role();
    return new User();
}
  • Role对象初始化:
    当spring调用AppConfig.role()方法,此时主方法是role(),因为AppConfig为cglib代理对象,所以这里调用role()方法时会走BeanMethodInterceptor的intercept()方法,此时isCurrentlyInvokedFactoryMethod会判断主方法AppConfig.role()和子调用方法AppConfig.role()相等,则会通过反射机制cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs)调用到实际对象AppConfig的role()方法完成Role对象的初始化

  • User对象初始化:
    当spring调用AppConfig.user()方法,此时主方法是user(),而内部又调用了role()方法,因为AppConfig为cglib代理对象,所以这里调用role()方法时又会走BeanMethodInterceptor的intercept()方法,此时isCurrentlyInvokedFactoryMethod会判断主方法AppConfig.user()和子调用方法AppConfig.role()不相等,于是就会走到方法,该方法返回BeanFactory中已经创建的Role对象,然后继续通过反射机制调用到实际对象AppConfig的user()方法完成User对象的初始化

②、方法先到BeanFactory中查找是否存在name等于role的bean,如果存在就直接返回

private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs, ConfigurableBeanFactory beanFactory, String beanName) {
	//从singleton集合中查找是否存在role bean
	Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :beanFactory.getBean(beanName));
	return beanInstance;
}

总结

Spring在5.2版本中引入了proxyBeanMethods,且默认值为true,即默认会对配置了@Configuration的类生成代理对象,文章前面我们看到SpringBoot2.2版本开始所有的AutoConfiguration类都会显示的配置@Configuration(proxyBeanMethods = false),这样做是有原因的,因为SpringBoot内部大量使用到自动配置特性,生成代理对象会使spring的启动时间增加,同时也会额外增加代理部分的对象内存

建议平时创建Configuration类时,参考SpringBoot给加上proxyBeanMethods =false属性,当然不加也是可以的,对项目本身性能影响也不大,看自己的习惯啦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值