How are proxy objects created in Spring AOP?

Today I will talk to my friends about how proxy objects are created in Spring AOP, and through this process we will get familiar with the Bean creation process.

The last few articles have been about talking to my friends about some details of using the Spring container. I will take a look at the source code based on these details. After reading all these functions, I will make another summary. You will have a more complete understanding of the entire creation process.

1. AOP usage

Let’s take a simple case first. Let’s review AOP first. Suppose I have the following class:

@Service 
public  class  UserService {

    public  void  hello () {
        System.out.println( "hello javaboy" );
    }
}

Then I wrote an aspect to intercept the methods in UserService:

@Component 
@Aspect 
@EnableAspectJAutoProxy 
public  class  LogAspect {

    @Before("execution(* org.javaboy.bean.aop.UserService.*(..))") 
    public  void  before (JoinPoint jp) {
         String  name  = jp.getSignature().getName();
        System.out.println(name+ "The method started executing..." );
    }
}

Finally, let’s take a look at the UserService object obtained from the Spring container:

ClassPathXmlApplicationContext  ctx  =  new  ClassPathXmlApplicationContext ( "aop.xml" );
 UserService  us  = ctx.getBean(UserService.class);
System.out.println( "us.getClass() = " + us.getClass());

The print result is as follows:

As you can see, the obtained UserService is a proxy object.

I won’t talk about other types of notifications here. Friends who are not familiar with them can reply to SSM in the background of the public account [Jiangnan Yidianyu]. There is a free introductory video recorded by Brother Song.

2. Principle analysis

So why does the UserService injected into the Spring container become a proxy object instead of the original UserService when obtained?

Overall, we can divide the life cycle of Spring Bean into four stages, namely:

  1. Instantiate.
  2. Property assignment.
  3. initialization.
  4. destroy.

First, instantiation is to create an instance of the Bean through reflection; next, attribute assignment is to assign values ​​to various properties of the created Bean; next, initialization is to provide various post-processors needed for the Bean application; and finally, is destroyed.

2.1 doCreateBean

The creation of the AOP proxy object is completed during the initialization process, so today we will start from the initialization here.

AbstractAutowireCapableBeanFactory#doCreateBean:

protected Object doCreateBean (String beanName, RootBeanDefinition mbd, @Nullable Object[] args) 
        throws BeanCreationException {
     //... 
    try {
        populateBean(beanName, mbd, instanceWrapper);
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    //... 
    return exposedObject;
}

Friends, you can see that there is an initializeBean method here, in which various post-processors are executed on the Bean:

protected Object initializeBean (String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
    invokeAwareMethods(beanName, bean);
    Object  wrappedBean  = bean;
     if (mbd == null || !mbd.isSynthetic()) {
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }
    try {
        invokeInitMethods(beanName, wrappedBean, mbd);
    }
    catch (Throwable ex) {
         throw  new  BeanCreationException (
                (mbd != null ? mbd.getResourceDescription() : null ), beanName, ex.getMessage(), ex);
    }
    if (mbd == null || !mbd.isSynthetic()) {
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }
    return wrappedBean;
}

A total of four methods are executed here, which are also very common Bean initialization methods:

  1. invokeAwareMethods: Execute beans under the Aware interface.
  2. applyBeanPostProcessorsBeforeInitialization: Execute the pre-method in BeanPostProcessor.
  3. invokeInitMethods: Execute the Bean’s initialization method init.
  4. applyBeanPostProcessorsAfterInitialization: Execute the post method in BeanPostProcessor.

1, 3 These two methods obviously have little to do with AOP. The AOP objects we usually create are basically processed in applyBeanPostProcessorsAfterInitialization. Let’s take a look at this method:

@Override 
public Object applyBeanPostProcessorsAfterInitialization (Object existingBean, String beanName) 
        throws BeansException {
     Object  result  = existingBean;
     for (BeanPostProcessor processor : getBeanPostProcessors()) {
         Object  current  = processor.postProcessAfterInitialization(result, beanName);
         if (current == null ) {
             return result;
        }
        result = current;
    }
    return result;
}

Friends, you can see that this is to traverse various BeanPostProcessors, execute their postProcessAfterInitialization method, assign the execution result to result and return it.

2.2 postProcessAfterInitialization

BeanPostProcessor has an implementation class AbstractAutoProxyCreator. In the postProcessAfterInitialization method of AbstractAutoProxyCreator, AOP processing is performed:

@Override 
public Object postProcessAfterInitialization ( @Nullable Object bean, String beanName) {
     if (bean != null ) {
         Object  cacheKey  = getCacheKey(bean.getClass(), beanName);
         if ( this .earlyProxyReferences.remove(cacheKey) != bean ) {
             return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}
protected Object wrapIfNecessary (Object bean, String beanName, Object cacheKey) {
     if (StringUtils.hasLength(beanName) && this .targetSourcedBeans.contains(beanName)) {
         return bean;
    }
    if (Boolean.FALSE.equals( this .advisedBeans.get(cacheKey))) {
         return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
         this .advisedBeans.put(cacheKey, Boolean.FALSE);
         return bean;
    }
    // Create proxy if we have advice. 
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null );
     if (specificInterceptors != DO_NOT_PROXY) {
         this .advisedBeans.put(cacheKey, Boolean.TRUE);
         Object  proxy  = createProxy(
                bean.getClass(), beanName, specificInterceptors, new  SingletonTargetSource (bean));
         this .proxyTypes.put(cacheKey, proxy.getClass());
         return proxy;
    }
    this .advisedBeans.put(cacheKey, Boolean.FALSE);
     return bean;
}

As you can see, it will first try to get the proxy object from the cache. If it is not in the cache, the wrapIfNecessary method will be called to create the AOP.

Normally, for the creation of ordinary AOP, the first three if conditions are not met. The first if is to say whether the beanName is a targetSource, which is obviously not the case here; the second if is to say whether the Bean does not require an agent (understood in conjunction with the previous article ), and we obviously need an agent here; the third if We have also introduced the function of if to our friends in the previous article , so we will not go into details here.

Regarding the second if, let me say one more thing. If what comes in here is an aspect bean, such as the LogAspect in the first section, this kind of bean obviously does not require a proxy, so it will be returned directly in the second method. If If it is another ordinary Bean, the second if will not come in.

In wrapIfNecessary, the most important methods are actually two: getAdvicesAndAdvisorsForBean and createProxy. The former is used to find all aspects that match the current class, and the latter is used to create proxy objects.

2.3 getAdvicesAndAdvisorsForBean

This method, to put it bluntly, is to find various Advice (notification/enhancement) and Advisor (aspect). Let’s see how to find it:

AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean:

@Override 
@Nullable 
protected Object[] getAdvicesAndAdvisorsForBean(
        Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
    List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
    if (advisors.isEmpty()) {
         return DO_NOT_PROXY;
    }
    return advisors.toArray();
}

As you can see from here, this method mainly calls findEligibleAdvisors to obtain all aspects, continue:

protected List<Advisor> findEligibleAdvisors (Class<?> beanClass, String beanName) {
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

There are three main methods here:

  • findCandidateAdvisors: This method is to query all candidate Advisors. To put it bluntly, it is to find all aspects registered in the Spring container when the project is started. Since there may be multiple Advices in an Aspect, each Advice can eventually be encapsulated into an Advisor. , so in the specific search process, after finding the Aspect Bean, you still need to traverse the methods in the Bean.
  • findAdvisorsThatCanApply: This method mainly filters out the aspects that can be applied to the current Bean based on the cut points from all the aspects found by the previous method.
  • extendAdvisors: This is to add a DefaultPointcutAdvisor aspect. The Advice used by this aspect is ExposeInvocationInterceptor. The function of ExposeInvocationInterceptor is to expose the MethodInvocation object to ThreadLocal. If the current MethodInvocation object needs to be used elsewhere, it can be taken out directly by calling the currentInvocation method.

Next, let’s take a look at the specific implementation of these three methods.

2.3.1 findCandidateAdvisors

AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors

@Override 
protected List<Advisor> findCandidateAdvisors () {
    List<Advisor> advisors = super .findCandidateAdvisors();
     if ( this .aspectJAdvisorsBuilder != null ) {
        advisors.addAll( this .aspectJAdvisorsBuilder.buildAspectJAdvisors());
    }
    return advisors;
}

The key to this method is to build all aspects through buildAspectJAdvisors. This method is a bit complicated:

public List<Advisor> buildAspectJAdvisors () {
    List<String> aspectNames = this .aspectBeanNames;
     if (aspectNames == null ) {
         synchronized ( this ) {
            aspectNames = this .aspectBeanNames;
             if (aspectNames == null ) {
                List<Advisor> advisors = new  ArrayList <>();
                aspectNames = new  ArrayList <>();
                String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                        this .beanFactory, Object.class, true , false );
                 for (String beanName : beanNames) {
                     if (!isEligibleBean(beanName)) {
                         continue ;
                    }
                    // We must be careful not to instantiate beans eagerly as in this case they 
                    // would be cached by the Spring container but would not have been weaved. 
                    Class<?> beanType = this .beanFactory.getType(beanName, false );
                     if (beanType == null ) {
                         continue ;
                    }
                    if ( this .advisorFactory.isAspect(beanType)) {
                        aspectNames.add(beanName);
                        AspectMetadata  amd  =  new  AspectMetadata (beanType, beanName);
                         if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                             MetadataAwareAspectInstanceFactory  factory  = 
                                    new  BeanFactoryAspectInstanceFactory ( this .beanFactory, beanName);
                            List<Advisor> classAdvisors = this .advisorFactory.getAdvisors(factory);
                             if ( this .beanFactory.isSingleton(beanName)) {
                                 this .advisorsCache.put(beanName, classAdvisors);
                            }
                            else {
                                 this .aspectFactoryCache.put(beanName, factory);
                            }
                            advisors.addAll(classAdvisors);
                        }
                        else {
                             // Per target or per this. 
                            if ( this .beanFactory.isSingleton(beanName)) {
                                 throw  new  IllegalArgumentException ( "Bean with name '" + beanName +
                                         "' is a singleton, but aspect instantiation model is not singleton" ) ;
                            }
                            MetadataAwareAspectInstanceFactory  factory  = 
                                    new  PrototypeAspectInstanceFactory ( this .beanFactory, beanName);
                             this .aspectFactoryCache.put(beanName, factory);
                            advisors.addAll( this .advisorFactory.getAdvisors(factory));
                        }
                    }
                }
                this .aspectBeanNames = aspectNames;
                 return advisors;
            }
        }
    }
    if (aspectNames.isEmpty()) {
         return Collections.emptyList();
    }
    List<Advisor> advisors = new  ArrayList <>();
     for (String aspectName : aspectNames) {
        List<Advisor> cachedAdvisors = this .advisorsCache.get(aspectName);
         if (cachedAdvisors != null ) {
            advisors.addAll(cachedAdvisors);
        }
        else {
             MetadataAwareAspectInstanceFactory  factory  =  this .aspectFactoryCache.get(aspectName);
            advisors.addAll( this .advisorFactory.getAdvisors(factory));
        }
    }
    return advisors;
}

When this method comes in for the first time, the aspectNames variable has no value, so it will first enter the if branch and assign values ​​to the two variables aspectNames and aspectBeanNames.

The specific process is to first call the BeanFactoryUtils.beanNamesForTypeIncludingAncestors method (for those who are not familiar with this method, please refer to the article What’s going on with parent-child containers in Spring? ), go to the current container and the parent container of the current container, find all the beanNames, and return The array is assigned to the beanNames variable, and then the beanNames are traversed.

When traversing, the isEligibleBean method is first called. This method is to check whether the Bean with a given name meets the conditions for automatic proxying. We will not look at this detail, because generally, the AOP in our project is automatically proxied.

Next, find the corresponding bean type beanType based on the beanName, and then call the advisorFactory.isAspect method to determine whether the beanType is an Aspect. The specific determination process has been described in the previous article , and friends can refer to it.

If the bean corresponding to the current beanName is an Aspect, then add the beanName to the aspectNames collection, and encapsulate the beanName and beanType into an AspectMetadata object.

Next, we will judge whether kind is SINGLETON. The default is SINGLETON, so here we will enter the branch. After entering, we will call this.advisorFactory.getAdvisorsthe method to find various notifications and cut points in Aspect and encapsulate them into Advisor objects and return them. Because of an aspect Multiple notifications may be defined in , so the eventually returned Advisor is a set, and finally the found Advisor set is stored in the advisorsCache cache.

The logic of the latter method is easy to understand. Find all the advisors corresponding to a certain aspect from the advisorsCache, store them in the advisors collection, and then return the collection.

In this way, we have found all Advisors.

One more thing, in 

the last article , Brother Song left an easter egg at the end. At that time, he said that ordinary Beans will go to the shouldSkip method, and this shouldSkip method will eventually go to buildAspectJAdvisors, so the 

last article did not Let’s analyze the buildAspectJAdvisors method with everyone. In fact, if buildAspectJAdvisors is called in the shouldSkip method, then the collection of Advisors is completed, and you can obtain them directly every time in the future.

2.3.2 findAdvisorsThatCanApply

Next, the findAdvisorsThatCanApply method is mainly to find the Advisor that can match the current Bean from many Advisors. Friends know that each Advisor contains a pointcut. Different pointcuts mean different interception rules, so now we need Match and check which Advisor the current class needs to match:

protected List<Advisor> findAdvisorsThatCanApply (
        List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
    ProxyCreationContext.setCurrentProxiedBeanName(beanName);
    try {
         return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
    }
    finally {
        ProxyCreationContext.setCurrentProxiedBeanName( null );
    }
}

This actually calls the static method AopUtils.findAdvisorsThatCanApply to find matching Advisors:

public  static List<Advisor> findAdvisorsThatCanApply (List<Advisor> candidateAdvisors, Class<?> clazz) {
     if (candidateAdvisors.isEmpty()) {
         return candidateAdvisors;
    }
    List<Advisor> eligibleAdvisors = new  ArrayList <>();
     for (Advisor candidate : candidateAdvisors) {
         if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
            eligibleAdvisors.add(candidate);
        }
    }
    boolean  hasIntroductions  = !eligibleAdvisors.isEmpty();
     for (Advisor candidate : candidateAdvisors) {
         if (candidate instanceof IntroductionAdvisor) {
             // already processed 
            continue ;
        }
        if (canApply(candidate, clazz, hasIntroductions)) {
            eligibleAdvisors.add(candidate);
        }
    }
    return eligibleAdvisors;
}

In this method, we will first determine whether the type of Advisor is an IntroductionAdvisor type. Advisors of the IntroductionAdvisor type can only be intercepted at the class level, and are not as flexible as PointcutAdvisor, so we are generally not IntroductionAdvisor, so we will eventually go to the last branch here. :

public  static  boolean  canApply (Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
     if (advisor instanceof IntroductionAdvisor ia) {
         return ia.getClassFilter().matches(targetClass);
    }
    else  if (advisor instanceof PointcutAdvisor pca) {
         return canApply(pca.getPointcut(), targetClass, hasIntroductions);
    }
    else {
         // It doesn't have a pointcut so we assume it applies. 
        return  true ;
    }
}

From here, friends can see that the IntroductionAdvisor type Advisor only needs to call ClassFilter to filter it. ClassFilter Song Ge has already introduced it in the previous article ( playing with programmatic AOP ). Friends, take a look here. The matching logic is also very easy! The PointcutAdvisor type Advisor will continue to call the canApply method for judgment:

public  static  boolean  canApply (Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
     if (!pc.getClassFilter().matches(targetClass)) {
         return  false ;
    }
    MethodMatcher  methodMatcher  = pc.getMethodMatcher();
     if (methodMatcher == MethodMatcher.TRUE) {
         // No need to iterate the methods if we're matching any method anyway... 
        return  true ;
    }
    IntroductionAwareMethodMatcher  introductionAwareMethodMatcher  =  null ;
     if (methodMatcher instanceof IntroductionAwareMethodMatcher iamm) {
        introductionAwareMethodMatcher = iamm;
    }
    Set<Class<?>> classes = new  LinkedHashSet <>();
     if (!Proxy.isProxyClass(targetClass)) {
        classes.add(ClassUtils.getUserClass(targetClass));
    }
    classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
    for (Class<?> clazz : classes) {
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
        for (Method method : methods) {
             if (introductionAwareMethodMatcher != null ?
                    introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions):
                    methodMatcher.matches(method, targetClass)) {
                return  true ;
            }
        }
    }
    return  false ;
}

Friends, take a look, here we first match according to the class, and if the match is successful, continue to match according to the method. If the method matcher is set to true, then just return true directly. Otherwise, load the current class, which is targetClass, and then Traverse all methods in targetClass, and finally call introductionAwareMethodMatcher.matchesthe method to determine whether the method matches the point cut point.

In this way, we have found all Advisors matching the current class from all Advisors.

2.3.3 extendAdvisors

This is to add a DefaultPointcutAdvisor aspect. The Advice used by this aspect is ExposeInvocationInterceptor. The function of ExposeInvocationInterceptor is to expose the MethodInvocation object to ThreadLocal. If the current MethodInvocation object needs to be used elsewhere, it can be taken out directly by calling the currentInvocation method.

The logic of this method is relatively simple, so I won’t post it here. Friends can check it out by themselves.

2.4 createProxy

After reading the getAdvicesAndAdvisorsForBean method, we have found the Advisor that suits us. Next, we continue to look at the createProxy method, which is used to create a proxy object:

protected Object createProxy (Class<?> beanClass, @Nullable String beanName,
         @Nullable Object[] specificInterceptors, TargetSource targetSource) {
     return buildProxy(beanClass, beanName, specificInterceptors, targetSource, false );
}
private Object buildProxy (Class<?> beanClass, @Nullable String beanName,
         @Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) {
     if ( this .beanFactory instanceof ConfigurableListableBeanFactory clbf) {
        AutoProxyUtils.exposeTargetClass(clbf, beanName, beanClass);
    }
    ProxyFactory  proxyFactory  =  new  ProxyFactory ();
    proxyFactory.copyFrom( this );
     if (proxyFactory.isProxyTargetClass()) {
         // Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios) 
        if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
             // Must allow for introductions; can't just set interfaces to the proxy's interfaces only. 
            for (Class<?> ifc : beanClass.getInterfaces()) {
                proxyFactory.addInterface(ifc);
            }
        }
    }
    else {
         // No proxyTargetClass flag enforced, let's apply our default checks... 
        if (shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass( true );
        }
        else {
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    proxyFactory.setTargetSource(targetSource);
    customizeProxyFactory(proxyFactory);
    proxyFactory.setFrozen( this .freezeProxy);
     if (advisorsPreFiltered()) {
        proxyFactory.setPreFiltered( true );
    }
    // Use original ClassLoader if bean class not locally loaded in overriding class loader 
    ClassLoader  classLoader  = getProxyClassLoader();
     if (classLoader instanceof SmartClassLoader smartClassLoader && classLoader != beanClass.getClassLoader()) {
        classLoader = smartClassLoader.getOriginalClassLoader();
    }
    return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));
}

I don’t know if this code looks familiar to you guys. This is the alternative AOP I posted earlier, programmatic AOP! The content is in one article, so you can just take a look at this source code yourself, so I won’t go into details.

Okay, after the above operation, the proxy object is created ~ This article is a general logic, and there are some particularly fine details that I have not sorted out with my friends. We have time and space, Brother Song will continue to complete the article and Introduce everyone