Spring Series.A Brief Analysis of AOP Principle

Keywords: Java Spring JDK xml

;

Introduction to using Spring AOP

The two core functions of Spring are IOC and AOP.It's convenient when we use Spring's AOP functionality.You just need to configure it below.

@Component
@Aspect
public class MyAspect {

//PointCut matching methods must be methods of bean s in Spring
//Pointcut s can be defined in the following ways or combined in && | and!.
//These entry points defined below can be combined through && ||

private static Logger logger = LoggerFactory.getLogger(MyAspect.class);

//*: The return value of the representative method can be of any type
//The entire expression matches any echo method under the controller package, and the method is optional
@Pointcut("execution(* com.csx.demo.spring.boot.controller.*.echo(..))")
public void pointCut1(){}

@Before("pointCut1()")
public void befor(){
    logger.info("Before advice vvvv...");
    logger.info("I want to do something...");
}
}

Then open the comment

//Automatically select the appropriate AOP proxy
//Traditional xml is configured like this: <Aop:aspectj-autoproxy/>

//ExpoProxy = true property set to true, which means exposing the dynamically generated proxy class to the ThreadLocal thread of the AopContext
//By AopContext.currentProxy(); Gets the generated dynamic proxy class.

//The proxyTargetClass property sets whether the dynamic proxy uses the JDK dynamic proxy or the CGlib proxy, true uses the CGlib proxy, and false uses the JDK dynamic proxy.

//Note: The following configuration may not be required if Spring Boot is used.AOP has been automatically turned on in the AopAutoConfiguration autoconfiguration class
//By default, the Spring Boot configuration takes precedence over the following configuration using the CGLIB dynamic proxy

@Configuration
@EnableAspectJAutoProxy(exposeProxy = true,proxyTargetClass = false)
public class AopConfig {

}

With the configuration above, a precompute is triggered when we call the echo method of any class under the controller package.Actually, this statement is not very accurate.Because the class we're calling is no longer our own class.Instead, the Spring framework generates classes through dynamic proxies.

Anyone who knows a little about Spring AOP will know that Spring's AOP is implemented through a dynamic proxy.So how does Spring generate dynamic proxy classes and weave Advice into proxy classes?What is the whole process?Following is an analysis of Spring's process of generating dynamic proxy classes.

It should be noted that this blog is designed to comb the entire AOP dynamic proxy process, and the details need to be seen by everyone themselves.

What did @EnableAspectJAutoProxy do

If you're asked to study AOP from scratch, you're confused and don't know where to start.But there's a trick to looking at Spring's code: if you're looking at a feature, you can start with the Enable comment that turns it on.Many of Spring's features are turned on through Enable annotations, so these annotations are certainly related to these features.

So here we can start with the @EnableAspectJAutoProxy comment and see what it does.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
    //Always use cglib dynamic proxy if set to true
    //Set to false to use jdk dynamic proxy for interfaces and cglib proxy for classes
	boolean proxyTargetClass() default false;
	boolean exposeProxy() default false;
}

Looking at the @Impoer annotation above, it's natural to think about going to the AspectJAutoProxyRegistrar class.

//AspectJAutoProxyRegistrar Source Code
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     * The main function is to register AnnotationAwareAspectJAutoProxyCreator
     */
    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //BeanDefinition registering AnnotationAwareAspectJAutoProxyCreator
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            //Add two generic proxyTargetClass es and exposeProxy to the BeanDefinition registered above
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }

}

We can see that there is nothing special about the above class, so we registered a BeanDefinition.If we go in and look at the source code of the AnnotationAwareAspectJAutoProxyCreator class, we can see that this class actually implements the InstantiationAwareBeanPostProcessor interface.Friends who are familiar with Spring's urinary nature are acutely aware that Spring may be a dynamic proxy for beans in postProcessBeforeInstantiation or postProcessAfterInstantiation methods.

Let's take this guess with us to see what AnnotationAwareAspectJAutoProxyCreator is doing.

AnnotationAwareAspectJAutoProxyCreator Generates Dynamic Proxy Classes

@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;
        }
    }
    // CustomTargetSource is not normally specified, so this code will not enter, so the key code is
    // In postProcessAfterInitialization
    // Create proxy here if we have a custom TargetSource.
    // Suppresses unnecessary default instantiation of the target bean:
    // The TargetSource will handle target instances in a custom fashion.
    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;
}

Here is the key code for creating a dynamic proxy class.

@Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                //This is the key code to create the code class
                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.
        //Getting advice for the current Bean configuration is key
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            //Create proxy
            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;
    }

Layer-by-layer dedug goes in and we can see the code below, where the JDK dynamic proxy and Cglib dynamic proxy commonly used in our mouth are generated.

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: " +
                        "Either an interface or a target is required for proxy creation.");
            }
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                //Generate JDK dynamic proxy
                return new JdkDynamicAopProxy(config);
            }
            //Generate Cglib dynamic proxy
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }
    /**
     * Determine whether the supplied {@link AdvisedSupport} has only the
     * {@link org.springframework.aop.SpringProxy} interface specified
     * (or no proxy interfaces specified at all).
     */
    private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
        Class<?>[] ifcs = config.getProxiedInterfaces();
        return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
    }

So far, we have briefly analyzed the generation process of Spring dynamic proxy classes.

PS: As you can learn about the InstantiationAwareBeanPostProcessor interface and the BeanPostProcessor interface, they are very important interfaces in Spring.With these two interfaces in mind, Spring has many "mysterious" functions that you can understand.

A brief summary

From the above analysis, we find that the entire Spring AOP process is fairly simple without looking at the details of AOP dynamic proxy class generation:

  • The @EnableAspectJAutoProxy annotation registers the AnnotationAwareAspectJAutoProxyCreator class through AopConfigUtils, a tool class that implements the InstantiationAware Bean PostProcessor interface and therefore performs a series of additional operations on beans before and after instantiation of beans.
  • AnnotationAwareAspectJAutoProxyCreator postProcessAfterInitialization will find all Advice s related to the current Bean, create corresponding dynamic proxy classes if found, and return to the original class if not found.

So the whole big process is that simple.

Some important classes:

  • @EnableAspectJAutoProxy;
  • AspectJAutoProxyRegistrar: Register AnnotationAwareAspectJAutoProxyCreator
  • AnnotationAwareAspectJAutoProxyCreator: AOP dynamic proxy automatically generated processing class, other similar classes are AspectJAwareAdvisorAutoProxyCreator and Infrastructure AdvisorAutoProxyCreator;
  • AopConfigUtils:AOP Configuration Tool Class
  • ProxyFactory: Agent Factory
  • AopProxy interface: Common implementation classes Objenesis CglibAopProxy, JdkDynamicAopProxy

Reference resources

Posted by nomadrw on Tue, 16 Jun 2020 18:45:04 -0700