Analyzing the principle of Spring's @ Configuration annotation attribute proxyBeanMethods

preface

There is an annotation @ Configuration in the Spring framework that appears frequently. If you still feel unfamiliar, we can go to the Spring boot autoconfigure package of SpringBoot. You can find that each Configuration class must have an @ Configuration annotation, in which there is an attribute proxyBeanMethods. What is its function? Today we will explore its principle through the source code

case

Common usage

@Configuration
public class AppConfig {
    @Bean
    public MyBean myBean() {
        // instantiate, configure and return bean ...
    }
}

Usage in 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();
	}
}

See the difference? A proxyBeanMethods attribute is added. What is the use of this attribute?

Give examples:

proxyBeanMethods = false

@Configuration(proxyBeanMethods = false)
public class AppConfig {
	@Bean
    public Role role() {
        System.out.println("initialization Role Object!");
        return new Role();
    }
    
    @Bean
    public User user() {
        Role role = this.role();
        return new User();
    }
}

Output result:
Initialize Role object!
Initialize Role object!

proxyBeanMethods = true

@Configuration(proxyBeanMethods = true)
public class AppConfig {
	@Bean
    public Role role() {
        System.out.println("initialization Role Object!");
        return new Role();
    }
    
    @Bean
    public User user() {
        Role role = this.role();
        return new User();
    }
}

Output result:
Initialize Role object!

Source code analysis

When spring starts, it will scan all classes with @ Component and @ Configuration annotations, define these classes as BeanDefinition and put them into the beanDefinitionMap collection. Then spring will do some post-processing of BeanFactory for these beandefinitions, including a class ConfigurationClassPostProcessor. If @ Configuration is configured on the class, CGLIB is used to generate an enhanced Config proxy class. When instantiating the internal Bean object, it will first find it in the spring container. If it exists, it will be returned directly for use

The source code is as follows:

Class ConfigurationClassPostProcessor

Entry method: postProcessBeanDefinitionRegistry()
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
	//Omit
	processConfigBeanDefinitions(registry);
}
Method: processConfigBeanDefinitions

Traverse the beanDefinitionNames collection to determine whether these classes are configured with the @ Configuration annotation

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	//ergodic
	for (String beanName : candidateNames) {
		//Judge whether there is @ Configuration annotation on the class defined by beanDef
		if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
		}
	}
}

Enter the ConfigurationClassUtils class and check the checkConfigurationClassCandidate method

Class ConfigurationClassUtils

Method: checkConfigurationClassCandidate()

This method will judge whether the @ Configuration annotation is configured on the bean class. If it is configured and the proxyBeanMethods Attribute is true, set an Attribute value (Configuration_class_Attribute - > FULL) for the corresponding beanDefinition of the class. Otherwise, the Attribute value is (Configuration_class_Attribute - > LITE). These are the two Configuration modes of @ Configuration: FULL and LITE

Note: this value will be used in later steps

public static boolean checkConfigurationClassCandidate(
			BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
	...
	Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
		//If the proxyBeanMethods property value is True, set the value to full
		if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
		}
		//If the value of proxyBeanMethods property is false, the temple fair is lite
		else if (config != null || isConfigurationCandidate(metadata)) {
			beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
		}
	...
}

Class ConfigurationClassPostProcessor

Entry method: postProcessBeanFactory()

By replacing the configuration class with a subclass enhanced by CGLIB, call the enhanceConfigurationClasses method for enhancement

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	//Omit
	//Enhance the configuration class and generate cglib proxy object
	enhanceConfigurationClasses(beanFactory);
	//Omit
}
Method: enhanceConfigurationClasses()

Focus on beandef Getattribute (configurationclassutils. Configuration_class_attribute). In the previous step, the attribute value has been set for the beanDefinition object. If the value is lite, the proxy object will not be generated. If the value is full (indicating that @ configuration is configured on the class and proxyBeanMethods=true), the cglib object will be generated. The format is:

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

The code is as follows:

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
	Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>()
	//Loop all BeanDefinition objects. Here we only look at AppConfig (example)
	for (String beanName : beanFactory.getBeanDefinitionNames()) {
		BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
		Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
		//...
		//If Configuration is configured and proxyBeanMethods=true, the cglib object will be generated; otherwise, the native object will be returned
		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();
		//Generate enhanced classes for class AppConfig
		Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
		if (configClass != enhancedClass) {
			//Set the type of AppConfig object to AppConfig$$EnhancerBySpringCGLIB
			beanDef.setBeanClass(enhancedClass);
		}
	}
}

From the code, we can see that the agent enhancement class is generated through the enhancement () method of the ConfigurationClassEnhancer class. Continue to look down

Class ConfigurationClassEnhancer

Method: enhance()
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
	//...
	Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
	return enhancedClass;
}
Method: 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;
}

The CALLBACKS variable in the base defines a specific interceptor object, which is declared in the class ConfigurationClassEnhancer, as follows:

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

The BeanMethodInterceptor we focus on is the key point of the problem mentioned at the beginning of the article. Through this interceptor class, we can find out whether the Role object already exists from beanfactory. If so, we will directly return it. Otherwise, we will create it. Continue to look down:

Class BeanMethodInterceptor

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

This is the way to determine whether the current call method and the main method are the same. Take the code as an example

@Bean
public Role role() {
	System.out.println("initialization Role Object!");
	return new Role();
}
    
@Bean
public User user() {
    Role role = this.role();
    return new User();
}
  • Role object initialization:
    When spring calls AppConfig role() method. At this time, the main method is role(). Because AppConfig is a cglib proxy object, the intercept() method of BeanMethodInterceptor will be used when calling the role() method. At this time, iscurrent invokedfactorymethod will judge the main method AppConfig role() and child call method AppConfig If the role() is equal, cglibmethodproxy. Will be generated through the reflection mechanism Invokesuper (enhanced configinstance, beanmethodargs) calls the role() method of the actual object AppConfig to complete the initialization of the role object

  • User object initialization:
    When spring calls AppConfig User() method. At this time, the main method is user() and the role() method is called internally. Because AppConfig is a cglib proxy object, the intercept() method of BeanMethodInterceptor will be used when calling the role() method. At this time, iscurrentyinvokedfactory method will judge the main method AppConfig User() and child call method AppConfig If the roles () are not equal, you will go to the ② method, which returns the Role object already created in BeanFactory, and then continue to call the user () method of the actual object AppConfig through the reflection mechanism to complete the initialization of the user object

② . method first go to BeanFactory to find out whether there is a bean with name equal to role. If there is, it will return directly

private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,
				ConfigurableBeanFactory beanFactory, String beanName) {
	//...
	//Find out whether the role bean exists from the singleton collection
	Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
							beanFactory.getBean(beanName));
	}
	return beanInstance;
}

summary

Spring introduces proxyBeanMethods in version 5.2, and the default value is true, that is, proxy objects will be generated for classes configured with @ Configuration by default. We saw SpringBoot2 Since version 2, all AutoConfiguration classes will display the Configuration @ Configuration(proxyBeanMethods = false). This is for a reason, because the automatic Configuration feature is widely used in SpringBoot. Generating proxy objects will increase the startup time of spring and increase the object memory of the proxy part

It is recommended to imitate SpringBoot and add proxyBeanMethods =false attribute when creating the Configuration class. Of course, it is OK not to add it, which has little impact on the project performance itself. It depends on your habits! 😁

If the point of view of the article is wrong, welcome to leave a message for discussion!

Keywords: Java Spring Spring Boot

Added by blckspder on Wed, 02 Feb 2022 17:56:20 +0200