Talk about Spring's Cglib proxy for @ Configuration

1. Phenomenon

As we all know, the configuration class in Spring is used to replace the configuration file. In the old days, we used XML configuration, but now most of them use JavaBean configuration.

A simple configuration class is as follows:

@Configuration
@ComponentScan("com.dh")
public class AppConfig {
	@Bean
	public Entity1 entity1(){
		return new Entity1();
	}
	@Bean
	public Entity2 entity2(){
		return new Entity2();
	}
}

This is a simple configuration class, nothing special

Now let's start our sample code:

Entity1.java:

public class Entity1 {
	public Entity1() {
		System.out.println("Entity1 is initing....");
	}
}

Entity2.java:

public class Entity2 {
	public Entity2() {
		System.out.println("Entity2 is initing....");
	}
}

AppConfig.java:

@Configuration
@ComponentScan("com.dh")
public class AppConfig {
	@Bean
	public Entity1 entity1(){
		return new Entity1();
	}
	@Bean
	public Entity2 entity2(){
		entity1();
		return new Entity2();
	}
}

After running the project at this time, the print result is:

Entity1 is initing....
Entity2 is initing....

When I put AppConfig If the @ Configuration annotation in Java is deleted, the print result is as follows:

Entity1 is initing....
Entity1 is initing....
Entity2 is initing....

You may wonder why "entity1 is initiating..." is not printed twice but only once when we do not delete the Configuration annotation. Why does it restore our cognition after removing the Configuration annotation?
This is the full and lite of the configuration class set in Spring, and then involves the Cglib proxy used in Spring

2. Analysis

Here, the reason is analyzed directly, and the source code will be analyzed below

First, there are two types of configuration classes in Spring:

* 1,full(have@Configuration Annotated,Full configuration class)
* 2,lite(Without@Configuration annotation,But with other configuration notes,for example Import,ComponentScan,Partial configuration class)

If the configuration class is lite, Spring will not proxy its configuration class, but will simply implement the functions of these configuration classes

If the configuration class is full, then the agent configuration class is called. When calling the method in the configuration class to get Bean, first go to the getBean in BeanFactory (return value type.class). If the container exists, it will not actually call the real method, directly return.

Pseudo code of agent configuration class:

// Cglib knows that
Enhancer enhancer = new Enhancer();
/* The proxy class inherits from the proxy class (that is, our configuration class) */
enhancer.setSuperclass(Configuration class to be proxied.class);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
/* Method interception. The method given here will be called back before executing the method */
enhancer.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // method.getReturnType(): gets the configuration return type Class of the method that the configuration class calls at present. If there is a container, it does not need to call the actual method of the configuration class directly, return directly.
        if(Objects.nonNull(Factory object.getBean(method.getReturnType()))){
            return Factory object.getBean(method.getReturnType());
        }
        // Only when the container does not exist will the method of the configuration class be called
        return methodProxy.invokeSuper(o,objects)
    }
});
Proxy class object = enhancer.create();

3. Source code

Here we are concerned about:

1,Where is the configuration class resolved?
2,Where does the configuration class judge whether it is a fully configured class?full/lite
3,Where are configuration classes proxied

Startup class:

public static void main(String[]args){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    applicationContext.register(AppConfig.class);
    applicationContext.refresh();
}

First of all, we need to know that the actual function code of the Spring container is in refresh, and the "invokebeanfactory postprocessors (beanfactory);" in the refresh code is discussed this time In this line of code

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
	// Enter this line of code	
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
		......
	}

Note that this involves Spring's beanfactoryprocessor. Beanfactoryprocessor is a callback provided by Spring when the Bean has not been initialized. Of course, Spring has also written a beanfactoryprocessor callback to implement configuration class resolution. This class is called ConfigurationClassPostProcessor, but we found after reading this class, ConfigurationClassPostProcessor directly implements the following interfaces:
BeanDefinitionRegistryPostProcessor. This BeanDefinitionRegistryPostProcessor inherits from beanfactory postprocessor, so it shows our ConfigurationClassPostProcessor
At the same time, the interface implementation of two interfaces is made. In its implementation interface, postProcessBeanDefinitionRegistry is used to analyze the function of configuration class, such as scanning package, introducing class and configuration, and postProcessBeanFactory is used as agent

So let's go back to the above code flow and enter its invokebeanfactoryprocessors method

Find the first invokebeanefinitionregistrypostprocessors (currentregistryprocessors, registry); Code, here is for configuration class configuration analysis Enter this line of code

private static void invokeBeanDefinitionRegistryPostProcessors(Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessBeanDefinitionRegistry(registry);
    }
}

At this time, there is only one piece of data [ConfigurationClassPostProcessor] in the postProcessBeanDefinitionRegistry method of ConfigurationClassPostProcessor

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    int registryId = System.identityHashCode(registry);
    if (this.registriesPostProcessed.contains(registryId)) {
        throw new IllegalStateException(
                "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
    }
    if (this.factoriesPostProcessed.contains(registryId)) {
        throw new IllegalStateException(
                "postProcessBeanFactory already called on this post-processor against " + registry);
    }
    this.registriesPostProcessed.add(registryId);

    processConfigBeanDefinitions(registry);
}

Enter its processConfigBeanDefinitions method:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    String[] candidateNames = registry.getBeanDefinitionNames();
    for (String beanName : candidateNames) {
        BeanDefinition beanDef = registry.getBeanDefinition(beanName);
        if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
            .....Many codes
        }
        // Determine whether the configuration class
        else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {	// line1
            configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
        }
    }
    .......Many codes
}

Note that here, Spring takes out the names of all beandefinitions in the container, and then traverses. Note that we can get our AppConfig, because we have entered the register in front, and then the first if is to judge whether it has been resolved. If so, do not operate. If not, do not operate
To determine whether the current class is a configuration class, we enter configurationclassutils Check configurationclasscandidate:

public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
    String className = beanDef.getBeanClassName();
    if (className == null || beanDef.getFactoryMethodName() != null) {
        return false;
    }

    AnnotationMetadata metadata;/* Here, you need to get the metadata information of the BeanDefinition. However, because the annotation BeanDefinition and XML BeanDefinition obtain the metadata in different ways, you need to judge their types respectively before obtaining them*/
    if (beanDef instanceof AnnotatedBeanDefinition &&
            className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
        // Can reuse the pre-parsed metadata from the given BeanDefinition...
        metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
    }
    else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
        // Check already loaded Class if present...
        // since we possibly can't even load the class file for this Class.
        Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
        metadata = new StandardAnnotationMetadata(beanClass, true);
    }
    else {
        try {
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
            metadata = metadataReader.getAnnotationMetadata();
        }
        catch (IOException ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Could not find class file for introspecting configuration annotations: " + className, ex);
            }
            return false;
        }
    }
    if (isFullConfigurationCandidate(metadata)) {
        beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);/*Set flag bit*/
    }
    else if (isLiteConfigurationCandidate(metadata)) {
        beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);/*Set flag bit*/
    }
    else {
        return false;
    }
    return true;
}

This method can be summarized as follows:

  • metadata is the meta information of the current class, including annotations
  • According to different types of the current BeanDefinition, different methods are used to obtain its meta information metadata
  • In the line of if (isFullConfigurationCandidate(metadata)), you will judge whether the @ Configuration annotation is included in the current meta information. If so, it is considered to be a complete Configuration class, and set the annotation bit value to full
  • Judge whether it is an interface in else if. If it is an interface, return false, and the whole code will return false. If the meta information contains the following annotations: Component, ComponentScan, Import, ImportResource, and @ Bean in the method, it is considered to be a configuration class, but not completely. Return a true and set the flag bit to lite
    Let's verify and enter the isFullConfigurationCandidate method:
public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
    return metadata.isAnnotated(Configuration.class.getName());
}

That's not enough explanation. Let's look at isLiteConfigurationCandidate:

public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
    // Do not consider an interface or an annotation...
    if (metadata.isInterface()) {
        return false;
    }

    // Any of the typical annotations found?
    for (String indicator : candidateIndicators) {
        if (metadata.isAnnotated(indicator)) {
            return true;
        }
    }

    // Finally, let's look for @Bean methods...
    try {
        return metadata.hasAnnotatedMethods(Bean.class.getName());
    }
    catch (Throwable ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
        }
        return false;
    }
}

The important thing is that where for traverses the judgment, candidatindicators is a collection with four elements, as we said above, and then judge whether the methods in the class contain @ Bean in the try. If so, it is also considered to be a configuration class, but not completely ha
So far, we have explained points 1 and 2

Let's take a look at where to act as an agent:

Let's go back to the invokeBeanFactoryPostProcessors method. We said that Spring handles the BeanDefinitionRegistryPostProcessor callback. Let's turn to the bottom of the invokeBeanFactoryPostProcessors method

We go into this method:

private static void invokeBeanFactoryPostProcessors(
        Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {
    for (BeanFactoryPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessBeanFactory(beanFactory);
    }
}

Note that the callback method here is different from our callback above. Although it is the same class, it implements two interfaces. Here, we enter the postProcessBeanFactory method in ConfigurationClassPostProcessor

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    int factoryId = System.identityHashCode(beanFactory);
    if (this.factoriesPostProcessed.contains(factoryId)) {
        throw new IllegalStateException(
                "postProcessBeanFactory already called on this post-processor against " + beanFactory);
    }
    this.factoriesPostProcessed.add(factoryId);
    if (!this.registriesPostProcessed.contains(factoryId)) {
        // BeanDefinitionRegistryPostProcessor hook apparently not supported...
        // Simply call processConfigurationClasses lazily at this point then.
        processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
    }

    enhanceConfigurationClasses(beanFactory);
    beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

Enter its enhanceConfigurationClasses method

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
    Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
        BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
        if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {    // Code line 1 - > Line1
            if (!(beanDef instanceof AbstractBeanDefinition)) {
                throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
                        beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
            }
            else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
                logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
                        "' since its singleton instance has been created too early. The typical cause " +
                        "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
                        "return type: Consider declaring such methods as 'static'.");
            }
            configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
        }
    }
    if (configBeanDefs.isEmpty()) {
        // nothing to enhance -> return immediately
        return;
    }

    ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
    for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
        AbstractBeanDefinition beanDef = entry.getValue();
        // If a @Configuration class gets proxied, always proxy the target class
        beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
        try {
            // Set enhanced subclass of the user-specified bean class
            Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
            if (configClass != null) {
                Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);   // Code line 2 - > line2
                if (configClass != enhancedClass) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " +
                                "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
                    }
                    beanDef.setBeanClass(enhancedClass);
                }
            }
        }
        catch (Throwable ex) {
            throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
        }
    }
}

Notice our code line 1 - > Line1:

This code does this

public static boolean isFullConfigurationClass(BeanDefinition beanDef) {
    return CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE));
}
// CONFIGURATION_ CLASS_ Value of full = full

Judge whether it is full. If it is full, it will be stored in the map collection configBeanDefs

Then, on line 2 ---- > line2: class <? > enhancedClass = enhancer.enhance(configClass, this.beanClassLoader); This string of code is the proxy class to get the current class (configuration class). We enter enhancer In the enhance method

public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
    if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Ignoring request to enhance %s as it has " +
                    "already been enhanced. This usually indicates that more than one " +
                    "ConfigurationClassPostProcessor has been registered (e.g. via " +
                    "<context:annotation-config>). This is harmless, but you may " +
                    "want check your configuration and remove one CCPP if possible",
                    configClass.getName()));
        }
        return configClass;
    }
    Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));        // line1
    if (logger.isDebugEnabled()) {
        logger.debug(String.format("Successfully enhanced %s; enhanced class name is: %s",
                configClass.getName(), enhancedClass.getName()));
    }
    return enhancedClass;
}

Enter the newEnhancer method at line1 here and click:

private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
    Enhancer enhancer = new Enhancer();
    /* Cglib In proxy, the proxy class inherits from the proxied class */
    enhancer.setSuperclass(configSuperClass);       // line1
    /* Set the interface. Note that BeanFactory can be called back in this interface */
    enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});       // line2
    enhancer.setUseFactory(false);
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);       // line3
    enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));   // line4
    enhancer.setCallbackFilter(CALLBACK_FILTER);        // line5
    enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());      // Interceptor type
    return enhancer;
}

Set the target class as proxy class at line1 because cglib is based on inheritance

Set some implementation interfaces at line2. Note that we have said in the analysis that before calling the method, getBean determines whether there is a factory, so there must be a factory,
At this time, we need to know that there are various xxxaware interface callbacks in Spring, and Spring will call back various objects. For example, when we implement beanfactory aware,
Then the setBeanFactory method will be called back and passed to the BeanFactory factory. Then let's take a look at the interface EnhancedConfiguration, which inherits BeanFactoryAware:

public interface EnhancedConfiguration extends BeanFactoryAware {
}

It means that our proxy class can get the Bean factory. It means that it is valid to judge whether there is a Bean after getting the Bean from the factory

Set the Bean name generation policy at line3

line4 set the bytecode generator. Here, Spring uses BeanFactoryAwareGeneratorStrategy, which inherits from the DefaultGeneratorStrategy class. Let's look at the transform method of BeanFactoryAwareGeneratorStrategy

protected ClassGenerator transform(ClassGenerator cg) throws Exception {
    ClassEmitterTransformer transformer = new ClassEmitterTransformer() {
        @Override
        public void end_class() {
            declare_field(Constants.ACC_PUBLIC, BEAN_FACTORY_FIELD, Type.getType(BeanFactory.class), null);
            super.end_class();
        }
    };
    return new TransformingClassGenerator(cg, transformer);
}

Pay attention to it_ Class method, which sets a field named $$BeanFactory and type BeanFactory. This field is used to store BeanFactory

line5 set the interceptor type

So far, we have explained the third point: where to proxy the configuration class

This analysis is only a guide and should not be used as the only basis

Keywords: Spring

Added by techbinge on Sat, 25 Dec 2021 19:20:05 +0200