Spring trivia: an opportunity to advance AOP

Today, let’s talk about an unpopular knowledge in Spring: Bean processing does not follow the normal process, but performs AOP in advance.

This article is a supplement to the previous article ( The name of Spring Bean has a hidden secret, so that the name will not be proxied ). If you have not read the previous article, it is recommended to read it first, which will help you better understand this article.

1. Bean creation process

In the previous article , Brother Song and everyone sorted out that in the process of Bean creation, the BeanPostProcessor will first be given a chance to return the proxy object:

@Override 
protected Object createBean (String beanName, RootBeanDefinition mbd, @Nullable Object[] args) 
        throws BeanCreationException {
     //Omitted. . . 
    try {
         // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. 
        Object  bean  = resolveBeforeInstantiation(beanName, mbdToUse);
         if (bean != null ) {
             return bean;
        }
    }
    catch (Throwable ex) {
         throw  new  BeanCreationException (mbdToUse.getResourceDescription(), beanName,
                 "BeanPostProcessor before instantiation of bean failed" , ex);
    }
    try {
         Object  beanInstance  = doCreateBean(beanName, mbdToUse, args);
         if (logger.isTraceEnabled()) {
            logger.trace( "Finished creating instance of bean '" + beanName + "'" );
        }
        return beanInstance;
    }
    //Omitted. . . 
}

Friends, the resolveBeforeInstantiation method here is to give BeanPostProcessor a chance to return the proxy object. In this method, the InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation method will eventually be triggered. In the postProcessBeforeInstantiation method, it will first determine whether the current bean is an AOP-related class. wait:

@Override 
public Object postProcessBeforeInstantiation (Class<?> beanClass, String beanName) {
     Object  cacheKey  = getCacheKey(beanClass, beanName);
     if (!StringUtils.hasLength(beanName) || ! this .targetSourcedBeans.contains(beanName)) {
         if ( this .advisedBeans.containsKey(cacheKey)) {
             return  null ;
        }
        if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
             this .advisedBeans.put(cacheKey, Boolean.FALSE);
             return  null ;
        }
    }
    
    TargetSource  targetSource  = getCustomTargetSource(beanClass, beanName);
     if (targetSource != null ) {
         if (StringUtils.hasLength(beanName)) {
             this .targetSourcedBeans.add(beanName);
        }
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
        Object  proxy  = createProxy(beanClass, beanName, specificInterceptors, targetSource);
         this .proxyTypes.put(cacheKey, proxy.getClass());
         return proxy;
    }
    return  null ;
}

Brother Song has already analyzed the content in the previous if branch with everyone in the previous article , so I won’t go into details here.

Here we mainly talk about the logic in getCustomTargetSource.

Let’s first talk about the circumstances under which it will go to the getCustomTargetSource method: the current Bean is not a proxy object, nor an AOP-related class, but an ordinary regular class, then it will go to the getCustomTargetSource method, and a TargetSource will not be found here. object, and then create a proxy object of the current bean based on the object and return it. If the proxy object is returned, the subsequent bean creation process will not be executed.

Let’s take a look at the source code of this method:

@Nullable 
protected TargetSource getCustomTargetSource (Class<?> beanClass, String beanName) {
     // We can't create fancy target sources for directly registered singletons. 
    if ( this .customTargetSourceCreators != null &&
             this .beanFactory != null && this .beanFactory .containsBean(beanName)) {
         for (TargetSourceCreator tsc : this .customTargetSourceCreators) {
             TargetSource  ts  = tsc.getTargetSource(beanClass, beanName);
             if (ts != null ) {
                 return ts;
            }
        }
    }
    // No custom TargetSource found. 
    return  null ;
}

As you can see, there is a customTargetSourceCreators variable in the current class AbstractAutoProxyCreator. Now we are traversing the variable and creating TargetSource objects through the TargetSourceCreator saved in this collection.

TargetSourceCreator is an interface. This interface has only one abstract class AbstractBeanFactoryBasedTargetSourceCreator. Let’s take a look at how the getTargetSource method in AbstractBeanFactoryBasedTargetSourceCreator is executed:

@Override 
@Nullable 
public  final TargetSource getTargetSource (Class<?> beanClass, String beanName) {
     AbstractBeanFactoryBasedTargetSource  targetSource  =
            createBeanFactoryBasedTargetSource(beanClass, beanName);
    if (targetSource == null ) {
         return  null ;
    }

    DefaultListableBeanFactory  internalBeanFactory  = getInternalBeanFactoryForBean(beanName);
     // We need to override just this bean definition, as it may reference other beans 
    // and we're happy to take the parent's definition for those. 
    // Always use prototype scope if demanded. 
    BeanDefinition  bd  = getConfigurableBeanFactory().getMergedBeanDefinition(beanName);
     GenericBeanDefinition  bdCopy  =  new  GenericBeanDefinition (bd);
     if (isPrototypeBased()) {
        bdCopy.setScope(BeanDefinition.SCOPE_PROTOTYPE);
    }
    internalBeanFactory.registerBeanDefinition(beanName, bdCopy);
    // Complete configuring the PrototypeTargetSource.
    targetSource.setTargetBeanName(beanName);
    targetSource.setBeanFactory(internalBeanFactory);
    return targetSource;
}

First, the TargetSource object is created through the createBeanFactoryBasedTargetSource method, which is an abstract method and will be implemented in subclasses in the future.

Next, the getInternalBeanFactoryForBean method will be called to create a new internal container internalBeanFactory. In essence, this internalBeanFactory is actually a child container, and the existing container will serve as the parent container of this child container.

The next step is to obtain the BeanDefinition corresponding to the current beanName, then configure the properties, register it in the internal container, and finally return the targetSource object.

Let’s take a look at the getInternalBeanFactoryForBean method here:

protected DefaultListableBeanFactory getInternalBeanFactoryForBean (String beanName) {
     synchronized ( this .internalBeanFactories) {
         return  this .internalBeanFactories.computeIfAbsent(beanName,
                name -> buildInternalBeanFactory(getConfigurableBeanFactory()));
    }
}

protected DefaultListableBeanFactory buildInternalBeanFactory (ConfigurableBeanFactory containingFactory) {
     // Set parent so that references (up container hierarchies) are correctly resolved. 
    DefaultListableBeanFactory  internalBeanFactory  =  new  DefaultListableBeanFactory (containingFactory);
     // Required so that all BeanPostProcessors, Scopes, etc become available.
    internalBeanFactory.copyConfigurationFrom(containingFactory);
    // Filter out BeanPostProcessors that are part of the AOP infrastructure, 
    // since those are only meant to apply to beans defined in the original factory.
    internalBeanFactory.getBeanPostProcessors().removeIf(beanPostProcessor ->
            beanPostProcessor instanceof AopInfrastructureBean);
     return internalBeanFactory;
}

This is actually a normal container creation, there is nothing much to say, but there are a few points to note:

  1. When calling the buildInternalBeanFactory method to build a container, the getConfigurableBeanFactory method will first be called to obtain the current container as the parent container. If the current container does not exist, an exception will be thrown. This means that when we provide a TargetSourceCreator instance ourselves, we must specify a container.
  2. After the internal container is created, all BeanPostProcessors of the AopInfrastructureBean type will be removed from the internal container, that is, the beans created by the internal container in the future will no longer use the AopInfrastructureBean type post-processor, because this type of post-processor mainly It is used to process AOP. Now, the AOP proxy is generated on the spot, and these post-processors are no longer needed.

Okay, this is the general principle of AOP advance generation. Next, Brother Song will write a case and let’s take a look at it together.

2. Practice

First, let’s define a TargetSource:

public  class  UserServiceTargetSource  extends  AbstractBeanFactoryBasedTargetSource {
     @Override 
    public Object getTarget ()  throws Exception {
         return getBeanFactory().getBean(getTargetBeanName());
    }

    @Override 
    public  boolean  isStatic () {
         return  true ;
    }
}

Regarding TargetSource itself, Brother Song has already introduced a lot to you in the previous Spring source code video, so I won’t go into details here.

Next customize TargetSourceCreator:

public  class  CustomTargetSourceCreator  extends  AbstractBeanFactoryBasedTargetSourceCreator {

    @Override 
    protected AbstractBeanFactoryBasedTargetSource createBeanFactoryBasedTargetSource (Class<?> beanClass, String beanName) {
         if (getBeanFactory() instanceof ConfigurableListableBeanFactory) {
             if (beanClass.isAssignableFrom(UserService.class)) {
                 return  new  UserServiceTargetSource ();
            }
        }
        return  null ;
    }
}

If the bean to be created is UserService, then a UserServiceTargetSource object will be returned.

Finally, and the most critical step, according to the previous analysis, TargetSourceCreator exists in a post-processor of the InstantiationAwareBeanPostProcessor type such as AnnotationAwareAspectJAutoProxyCreator. Therefore, we must find a way to set the custom TargetSourceCreator to AnnotationAwareAspectJAutoProxyCreator, as follows:

@Component 
public  class  SetCustomTargetSourceCreator  implements  BeanPostProcessor , PriorityOrdered, BeanFactoryAware {

    private BeanFactory beanFactory;

    @Override 
    public  int  getOrder () {
         return Integer.MIN_VALUE;
    }

    @Override 
    public Object postProcessAfterInitialization (Object bean, String beanName)  throws BeansException {
         if (bean instanceof AnnotationAwareAspectJAutoProxyCreator) {
             AnnotationAwareAspectJAutoProxyCreator  annotationAwareAspectJAutoProxyCreator  = (AnnotationAwareAspectJAutoProxyCreator)bean;
             CustomTargetSourceCreator  customTargetSourceCreator  =  new  CustomT objectSourceCreator ();
            customTargetSourceCreator.setBeanFactory(beanFactory);
            annotationAwareAspectJAutoProxyCreator.setCustomTargetSourceCreators(customTargetSourceCreator);
        }
        return bean;
    }

    @Override 
    public  void  setBeanFactory (BeanFactory beanFactory)  throws BeansException {
         this .beanFactory = beanFactory;
    }
}

AnnotationAwareAspectJAutoProxyCreator itself is a BeanPostProcessor. What we have to do now is to modify this BeanPostProcessor. The BeanPostProcessor is initialized in the refresh method when the Spring container starts. During the entire initialization process, when did Song Ge intervene in the creation of the Bean before the BeanPostProcessor? It has been introduced in detail in one article.

When initializing BeanPostProcessor, first initialize those that implement the PriorityOrdered interface, then initialize those that implement the Ordered interface, and finally initialize those BeanPostProcessors that do not implement any sorting interface.

Our SetCustomTargetSourceCreator must be initialized before AnnotationAwareAspectJAutoProxyCreator. In this way, when AnnotationAwareAspectJAutoProxyCreator is initialized, a post-processor such as SetCustomTargetSourceCreator will be used, and the properties of AnnotationAwareAspectJAutoProxyCreator will be modified in the processor.

The AnnotationAwareAspectJAutoProxyCreator class indirectly implements the Ordered interface, and the default priority is the lowest. However, when the Spring container starts, when processing the BeanFactoryPostProcessor (specifically, ConfigurationClassPostProcessor), its priority is set to the highest.

Therefore, if we want the customized SetCustomTargetSourceCreator to be executed before AnnotationAwareAspectJAutoProxyCreator, then we can only let SetCustomTargetSourceCreator implement the PriorityOrdered interface. After implementing the PriorityOrdered interface, rewrite the getOrder method. It does not matter what the return value of this method is, it will be there anyway. BeanPostProcessor that implements the Ordered interface is executed before.

Finally, we can turn on the automatic proxy on the startup class:

@Configuration 
@ComponentScan 
@EnableAspectJAutoProxy 
public  class  JavaConfig {
}

You’re done.

In this way, when the Spring container creates a Bean, it will be intercepted by the BeanPostProcessor in advance, and then a TargetSource will be given, and a proxy object will be created accordingly, so that there is no need for subsequent regular Bean creation processes. Okay, interested friends can try it themselves~