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!