Spring 5 Source Parsing 7-Configuration Class Post Processor

Posted by mrbill501 on Thu, 10 Oct 2019 06:29:08 +0200

Configuration ClassPostProcessor inherits the BeanDefinitionRegistryPostProcessor interface, which implements the postProcessBeanDefinitionRegistry and its parent BeanFactoryPostProcessor#postProcessBeanFactory method.

For an analysis of the postProcessBean Definition Registry method, see: Spring 5 Source Learning (5) Configuration Class Post Processor (1).

Now let's look at the source code for the Configuration ClassPostProcessor # postProcessBeanFactory method.

ConfigurationClassPostProcessor#postProcessBeanFactory

Timing of invocation

The Configuration ClassPostProcessor postProcessBeanFactory method is also refresh(); invokeBeanFactoryPostProcessors(beanFactory) is executed in the method; the method is called.

Source code parsing

//Configuration ClassPostProcessor#postProcessBeanFactory source code
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);
    // In this. postProcessBean Definition Registry (Bean Definition Registry) method
    // Call this. registries PostProcessed. add (registryId);
    // if condition is not established
    if (!this.registriesPostProcessed.contains(factoryId)) {
        // BeanDefinitionRegistryPostProcessor hook apparently not supported...
        // Simply call processConfigurationClasses lazily at this point then.
        processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
    }

    // Enhancement of configuration classes
    enhanceConfigurationClasses(beanFactory);
    // Create ImportAware BeanPostProcessor to support ImportAware and call the ImportAware.setImportMetadata method
    beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

Two main things have been done:

  1. Enhancement of configuration classes
  2. Create Import Aware Bean Post Processor to support the Import Aware interface.

Look at the source code of the enhanceConfiguration Classes (beanFactory), which enhances the configuration class.

Enhance Configuration Classes (beanFactory) Enhance Full Configuration

Spring proxies Full Configuration to intercept the @Bean method to ensure that the @Bean semantics are properly handled. This enhanced proxy class is generated in the enhanceConfiguration Classes (beanFactory) method with the following source code:

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
    Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
    //Get all bean Definition Name, which has been scanned before, and all beanName will be obtained here.
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
        BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
        // Check whether it is Full Configuration Class, that is, whether it is marked @Configuration
        if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
            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.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
                logger.info("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'.");
            }
            //If it's Full Configuration Class, put it in the variable configBeanDefs
            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) {
                // Enhancement of Full Configuration Class
                Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
                if (configClass != enhancedClass) {
                    if (logger.isTraceEnabled()) {
                        logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
                                "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
                    }
                    //Set BeanClass to Enhanced Class
                    beanDef.setBeanClass(enhancedClass);
                }
            }
        } catch (Throwable ex) {
            throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
        }
    }
}

Get all Bean Definition for Full Configuration Class (that is, the configuration class labeled @Configuration), then call enhancer. enhance (configClass, this. bean Class Loader) in turn; method, enhance the configuration class, return the method to Class <?> enhancedClass and set it to Bean Definition (eanDef. BeanClass (enhancedClass); Spring creates the Bean Definition This enhancement class is used to create it.

The following is enhancer.enhance(configClass, this.beanClassLoader); method source code:

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));
    if (logger.isTraceEnabled()) {
        logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s",
                configClass.getName(), enhancedClass.getName()));
    }
    return enhancedClass;
}

Create Full Configuration Enhancement Class

Specifically, take a look at the new Enhancer (configClass, classLoader method), which is responsible for creating Full Configuration enhancement classes.

private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
    // Spring repackaged CGLIB (using Spring-specific patches; for internal use only)
    // This avoids any potential conflicts with CGLIB dependencies at the application level or on third-party libraries and frameworks
    // https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/cglib/package-summary.html
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(configSuperClass);
    // Set up the interfaces that need to be implemented, that is, the Enhanced Configuration interface that our configuration class's cglib agent also implements
    enhancer.setInterfaces(new Class<?>[]{EnhancedConfiguration.class});
    enhancer.setUseFactory(false);
    // Setting Naming Policy
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
    // Setting Generator to Create Bytecode Policy
    // BeanFactory Aware Generator Strategy is a custom extension of CGLIB's Default Generator Strategy, mainly for introducing BeanFactory fields.
    enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
    // Settings Enhancement
    enhancer.setCallbackFilter(CALLBACK_FILTER);
    enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
    return enhancer;
}

The Enhancer object here is org. spring framework. cglib. proxy. Enhancer. What is its relationship to cglib?

Spring's repackaging of CGLIB 3.2 (with Spring-specific patches; for internal use only).This repackaging technique avoids any potential conflicts with dependencies on CGLIB at the application level or from third-party libraries and frameworks.

Quoted from: https://docs.spring.io/spring...

In general, Spring repackaged CGLIB (using Spring patches for internal use only) to avoid any potential conflicts with CGLIB dependencies at the application level or on third-party libraries and frameworks.

What specific enhancements have been made?

  1. Implement the Enhanced Configuration interface. This is an empty flag interface, used only internally within the Spring framework, and implemented by all @Configuration CGLIB subclasses, which inherits the BeanFactoryAware interface.
  2. Naming policy is set
  3. Set the policy for the generator to create bytecodes. BeanFactory Aware Generator Strategy inherits cglib's Default Generator Strategy, whose main purpose is to introduce subclasses into BeanFactory fields and set ClassLoader.
  4. Set Enhanced Callback:
private static final Callback[] CALLBACKS = new Callback[]{
        // Intercept calls to @Bean methods to ensure proper handling of @Bean semantics
        new BeanMethodInterceptor(),
        // Intercepting calls to BeanFactoryAware#setBeanFactory
        new BeanFactoryAwareMethodInterceptor(),
        NoOp.INSTANCE
};
  • BeanMethod Interceptor: Responsible for intercepting calls to @Bean methods to ensure proper handling of @Bean semantics.
  • BeanFactoryAware Method Interceptor: Responsible for intercepting calls to the BeanFactoryAware#setBeanFactory method because the enhanced configuration class implements the Enhanced Configuration interface (that is, the BeanFactoryAwar interface).

Setting up Enhanced Callback

Next, let's take AppConfig as an example to learn how to enhance Callback related source code.

@Configuration
@ComponentScan
public class AppConfig {

    @Bean
    public String name() throws Exception {
        getUserBean().getObject();
        return "Programmer Xiao Hei";
    }


    @Bean
    public FactoryBean getUserBean() {
        return new FactoryBean<UserBean>() {
            @Override
            public UserBean getObject() throws Exception {
                System.out.println("1111");
                return new UserBean("shen", 17);
            }

            @Override
            public Class<?> getObjectType() {
                return UserBean.class;
            }
        };
    }
}
BeanMethodInterceptor

The main function is to intercept calls to the @Bean method to ensure that the @Bean semantics are handled correctly. When the @Bean method is called, it is intercepted by the following code:

//BeanMethod Interceptor# intercept source code
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
                        MethodProxy cglibMethodProxy) throws Throwable {

    // Enhanced ConfigInstance is already an enhancement object for configuration classes, in which there are beanFactory fields
    // Get the beanFactory in the enhanced object
    ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
    // Get the bean Name
    String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

    // Determine whether this bean is a scoped-proxy
    if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
        String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
        if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
            beanName = scopedBeanName;
        }
    }

    // To handle the case of an inter-bean method reference, we must explicitly check the
    // container for already cached instances.

    // First, check to see if the requested bean is a FactoryBean. If so, create a subclass
    // proxy that intercepts calls to getObject() and returns any cached bean instance.
    // This ensures that the semantics of calling a FactoryBean from within @Bean methods
    // is the same as that of referring to a FactoryBean within XML. See SPR-6602.

    // Check if the corresponding FactoryBean exists in the container, and if so, create an enhancement class
    // Proxy intercepts calls to getObject () by creating enhancement classes to ensure the semantics of FactoryBean s
    if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
            factoryContainsBean(beanFactory, beanName)) {
        Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
        if (factoryBean instanceof ScopedProxyFactoryBean) {
            // Scoped proxy factory beans are a special case and should not be further proxied
        } else {
            // It is a candidate FactoryBean - go ahead with enhancement
            // Create enhancement classes to proxy calls to getObject ()
            // There are two alternative proxies, cglib and jdk
            // Proxy.newProxyInstance(
            //                    factoryBean.getClass().getClassLoader(), new Class<?>[]{interfaceType},
            //                    (proxy, method, args) -> {
            //                        if (method.getName().equals("getObject") && args == null) {
            //                            return beanFactory.getBean(beanName);
            //                        }
            //                        return ReflectionUtils.invokeMethod(method, factoryBean, args);
            //                    });
            return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
        }
    }

    // Determine if the method being executed is the @Bean method itself
    // For example, if you call the @Bean method directly, or Spring to call our @Bean method, you return true.
    // If it's inside another method, our own program calls the @Bean method and returns false.
    if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
        // The factory is calling the bean method in order to instantiate and register the bean
        // (i.e. via a getBean() call) -> invoke the super implementation of the method to actually
        // create the bean instance.
        if (logger.isInfoEnabled() &&
                BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
            logger.info(String.format("@Bean method %s.%s is non-static and returns an object " +
                            "assignable to Spring's BeanFactoryPostProcessor interface. This will " +
                            "result in a failure to process annotations such as @Autowired, " +
                            "@Resource and @PostConstruct within the method's declaring " +
                            "@Configuration class. Add the 'static' modifier to this method to avoid " +
                            "these container lifecycle issues; see @Bean javadoc for complete details.",
                    beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
        }
        // If true is returned, that is, Spring is calling the method, then the method is actually executed.
        return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
    }

    //Otherwise, try to get the Bean object from the container
    // How do I get it? By calling the beanFactory.getBean method
    // The getBean method returns directly if the object has been created, and if it has not been created, it creates, puts it in the container, and returns.
    return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
  1. Enhanced ConfigInstance is an enhancement object for configuration classes. Get the beanFactory and beanName from the enhancement object. For example, when Spring calls the name() method, the bean Name is the name.
  2. Check if the corresponding FactoryBean exists in the container, and if so, create an enhancement class to proxy the call to getObject(). In this example, the program will not perform this step if the reader deletes the name() method comment. Because when Spring calls the getUserBean() method, there is no corresponding FactoryBean in the container. Because only the second call to getUserBean() method container will have the corresponding FactoryBean.
  3. Determine whether the method being executed was the @Bean method itself, and if so, call the method directly without enhanced interception; otherwise, try to get the bean object from the container.
BeanFactoryAwareMethodInterceptor

The BeanFactoryAware Method Interceptor method is relatively simple, and its function is to intercept calls to BeanFactoryAware setBeanFactory for obtaining BeanFactory objects.

// BeanFactoryAware Method Interceptor # intercept source code
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD);
    Assert.state(field != null, "Unable to find generated BeanFactory field");
    field.set(obj, args[0]);

    // Does the actual (non-CGLIB) superclass implement BeanFactoryAware?
    // If so, call its setBeanFactory() method. If not, just exit.
    if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
        return proxy.invokeSuper(obj, args);
    }
    return null;
}

Output Enhancement class File

Finally, to add another point, we can obtain the class file of the CGLIB proxy enhancement class that Spring generated for us by configuring as follows:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "spring-study/docs/classes");

Source comment GITHUB address: https://github.com/shenjianen...

Welcome to pay attention to the public number and learn and grow together.

Topics: Java Spring github xml JDK