The core elements of Spring are IoC and AOP. AOP, in turn, supports many Spring dependencies, including Transcation, Lazy Loading, Caching, and so on. Spring has been around for a few years, and there have been a number of big names who have explained the process in detail, but today, I want to give you a little more detail on how Spring AOP works from my perspective.

Before the dishes

Before getting into the specifics of AOP implementation, let’s talk a little bit about Cglib. Cglib is a code generation library that is implemented based on ASM at the bottom and allows the creation of classes after the program is compiled, which is the key to Spring implementing AOP class proxies.

Let’s take a quick look at a simple implementation of proxying a class using Cglib

public class CglibEnhancerDemo {

    public static void main(String[] args) {
        // create Enhancer to prepare for the enhancement class creation
        Enhancer enhancer = new Enhancer();
        // Specify a ClassLoader for the enhanced class
        enhancer.setClassLoader(Thread.currentThread().getContextClassLoader());
        The Cglib proxy class is implemented by inheriting the target class that needs to be enhanced.
        enhancer.setSuperclass(ProxyTargetClass.class);
        /* * Sets the callback when a method is called. This is just a simple configuration
        enhancer.setCallback((MethodInterceptor)(o, method, objects, methodProxy) -> {
            // do something before method invoke
            System.out.println("Before method invoke");
            Object result = methodProxy.invokeSuper(o, objects);
            System.out.printf("Complete invoke method, result is : %s\n", result);
            return result;
        });
	    // Create the enhanced class
        ProxyTargetClass targetClass = (ProxyTargetClass)enhancer.create();
        System.out.println(targetClass.saySomeThing());
        /* * Before method invoke * Complete invoke method, result is : Hello, ShawJie * Hello, ShawJie */}}class ProxyTargetClass {
    private static final String name = "ShawJie";
    public String saySomeThing(a) {
        return "Hello, "+ name; }}Copy the code

AOP implementation of Spring

After a quick look at how Cglib enhances classes, let’s now look at how Spring uses Cglib to enhance classes. To ensure overall consistency, all logic analysis here is based on the Spring-boot (v2.2.9.RELEASE) -somke-test-AOP project. Git address: github.com/spring-proj… .

Spring uses the @aspect annotation to declare aspects, and implements the overall logic of class enhancement with @before, @After, @around, @pointcut, etc. In fact, both the scanning process of @aspect annotations and other aspects need to go through the Spring Bean scanning assembly process. However, this process is not the focus of this article, so it will not be analyzed too much in this article (maybe we will look at it together later). Interested students can also go to know about it first. Scan register core method: ClassPathBeanDefinitionScanner# doScan, instantiate the core methods: ConfigurableListableBeanFactory# preInstantiateSingletons

Advisor’s scan timing

  • Before I knew anything about Spring Aop, I only knew that classes would be propped by Cglib/JDK proxies. I imagined several possible implementations of the aspect:

    1. After the class is propped up, Cglib attaches a generic implementation to the method, which dynamically obtains and executes the enhanced logic for the method. The advantage of this implementation is that dynamic control can be applied to the method aspect at application runtime, adding or subtracting operations from the front. However, the disadvantages are also obvious. Before each method is executed, it is necessary to parse the matching rules of the facets and find out the list of facets that are consistent with the current method, which greatly reduces the execution efficiency of the method.

    2. After the class is proxied, the section is matched and obtained, and the obtained section list is dynamically constructed into the corresponding Callback list of the method through Cglib. The advantage of this implementation is to make up for the deficiency of reducing the execution efficiency of the method by dynamically obtaining the section list, but at the same time, it is not convenient to dynamically manage the section of the method at runtime.

Once you’ve imagined how the two sections would be executed, either way, you need to scan all the facets in your application and build a list of facets. Spring-aop does the same, with Spring using the AutoxyCreator post-processor to scan and build aspects within an application before instantiating the first singleton Bean.

Which come of AutoProxyCreator

Before taking a closer look at how the AutoxyCreator post processor scans the build Advisor list, let’s take a look at where the AutoxyCreator post processor came from. You probably know that you can enable Aop functionality by configuring the @EnableAspectJAutoProxy annotation, but why does SpringBoot implement Aop functionality without this annotation? This involves the Spring autowiring mentioned in the previous article. If you are interested, you can click here to have a look at the autowiring logic and process. Here, you will not go into too much detail and directly focus on the core.

The spring.factories file of the spring-auto-configure package configures the configuration classes that need to be auto-assembled. Including org. Springframework. Boot. Autoconfigure. Aop. AopAutoConfiguration, AopAutoConfiguration class we can see the automatic assembly of one of the core @ Conditional annotation.

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {... }Copy the code

This annotation indicates that the configuration content that executes the configuration class is loaded by default, and that Spring does not assemble AOP-related functionality only if spring.aop.auto = false is configured.

@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
static class CglibAutoProxyConfiguration {}Copy the code

Under the configuration of AopAutoConfiguration classes have a CglibAutoProxyConfiguration, by the same token, the annotation indicates that by default (without any configuration) to load the current class configuration. On this class we see the familiar @enableAspectJAutoProxy annotation, which is why we don’t need to display this annotation in our Spring-Boot application.

@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
  ...
}

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if(enableAspectJAutoProxy ! =null) {
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); }}}Copy the code

Within the @ EnableAspectAutoProxy annotations introduced AspectJAutoProxyRegistrar registry, Within the registry registry logic we can see that by calling registerAspectJAnnotationAutoProxyCreatorIfNecessary to registered within the BeanFactory AopProxyCreator post-processor.

True true scan logic

Bean instantiation phase in the Spring, Spring will call before instantiating all BeanPostProcessor postProcessBeforeInstantiation method in the application to some processing before the Bean is instantiated. Let’s take a look at what the post-processing of AopxyCreator does before instantiation.

@Override
public Object postProcessBeforeInstantiation(Class
        beanClass, String beanName) {
  // The cache name of the build bean
  Object cacheKey = getCacheKey(beanClass, beanName);

  // Determine if the Bean is responsible for the custom TargetSource
  if(! StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
    // Determine whether the current Bean has already been processed
    if (this.advisedBeans.containsKey(cacheKey)) {
      return null;
    }
    // Check if it is an Aop base class (Advice/Pointcut/Advisor or a subclass)
    // If shouldSkip returns true, it should not be handled by the current postProessor
    if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return null; }}/** * custom TargetSource is an advanced feature of SpringAop * Aop proxies not your beanTraget but TargetSource * this feature is not the focus of this article so I won't explain it too much * more later * /
  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;
}
Copy the code

AopProxyCreator’s bean instantiation pre – and post-processing only looks at the logic on the surface, as if the focus is only on the logic of CustomeTargetSource. But in the implementation class AopProxyCreator AnnotationAwareAspectJAutoProxyCreator, shouldSkip method be rewritten and increase to scan was marked in the beanFactory @ Ascpet build logic.

// AspectJAwareAdvisorAutoProxyCreator#shouldSkip
@Override
protected boolean shouldSkip(Class
        beanClass, String beanName) {
  List<Advisor> candidateAdvisors = findCandidateAdvisors();
  for (Advisor advisor : candidateAdvisors) {
    if (advisor instanceof AspectJPointcutAdvisor &&
        ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
      return true; }}return super.shouldSkip(beanClass, beanName);
}
Copy the code
// AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors
@Override
protected List<Advisor> findCandidateAdvisors(a) {
  // Find the list of Advisor implementation classes in beanFactory
  List<Advisor> advisors = super.findCandidateAdvisors();
  if (this.aspectJAdvisorsBuilder ! =null) {
   	// Look in beanFactory for the Aspect bean marked with the @aspect annotation
    advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
  }
  return advisors;
}
Copy the code

We’ll focus on the buildAspectJAdvisors method, which we won’t post here because we have a lot of code, but we’ll briefly outline the scan process here, and those of you who are interested in the logic can open the IDE and take a look.

  1. Double-Check-LockCheck cachedaspectBeanNamesIf yes, the initialization process is performed
  2. fromBeanFactoryFish all beans in
  3. Determine if the bean is typed@Aspectannotations
  4. toaspectBeanNamesaddbeanNameCache. If the aspect class is a singleton, it is placed directly as cacheadvisorsCache, and vice versaadvisorFactoryIn theaspectFactoryCacheAs factory cache

Enhance the construction of classes

The Advisor scan logic in the previous paragraph has been completed, and all aspects of our system have been cached by Spring. In this section we’ll jump right into the instantiation of Spring beans and see how Spring AOP enhances beans during the instantiation process.

In AbstractAutowireCapableBeanFactory# initializeBean to Bean finally instantiation, After the instantiation within called when the application processor postProcessAfterInitialization method to instantiate the post-processing logic Bean. AOP’s BeanPostProcessor(abstracta to xycreator) also implements this method.

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
  if(bean ! =null) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    // Check the loop dependency logic
    if (this.earlyProxyReferences.remove(cacheKey) ! = bean) {// Check the bean. Qualified beans are enhanced
      returnwrapIfNecessary(bean, beanName, cacheKey); }}return bean;
}
Copy the code
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // Check whether the Bean is responsible for the custom TargetSource
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    // Check whether the Bean has been checked and the result is that no enhancement is required
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    // Check if it is an Aop base class (Advice/Pointcut/Advisor or a subclass)
    // If shouldSkip returns true, it should not be handled by the current postProessor
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // Find advice related to the current Bean. If there is advice related to the Bean, enhance the Bean with that 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;
}
Copy the code
  • There are two important points in this logic to examine and enhance beans

    1. How does Spring find the Advice associated with the bean meta-information in the Advice list that we scanned and cached earlier

    2. How does Spring enhance classes

Who is your Advice

After Spring retrieves all Advice from the cache, it sends the Advice list and Bean meta-information to AopUtils#findAdvisorsThatCanApply for lookup and filtering and returns a matching subset of Advice. Its internal logic by org. Aspectj. Weaver. Patterns. Pointcut# match is responsible, and specific logic will be passed on to PointCut subclasses implement, such as processing @ the annotation (…). AnnotationPointcut, and execution(…) KindedPointcut and so on, the specific logic is too long to explain in detail here, interested students can go to read. Ps: about the Pointcut expression parsing logic can refer PointcutParser# resolvePointcutExpression

It’s ready so it’s gonna go in

After getting the matching Advice subset, the final task is to build the subset into interceptors and CGLIB to implement object enhancement. Let’s jump right to CGLIB’s enhanced class building process: CglibAopProxy#getProxy

// We skim some unnecessary code, leaving only the parts we need to care about
public Object getProxy(@Nullable ClassLoader classLoader) {
    try{ Class<? > rootClass =this.advised.getTargetClass(); Class<? > proxySuperClass = rootClass;// Check whether the target class to be enhanced has already been enhanced. If so, get its parent as the parent of CGLIB
        if(rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) { proxySuperClass = rootClass.getSuperclass(); Class<? >[] additionalInterfaces = rootClass.getInterfaces();for(Class<? > additionalInterface : additionalInterfaces) {this.advised.addInterface(additionalInterface); }}// If you feel a bit familiar with CGLIB's enhanced class creation logic here it comes
        Enhancer enhancer = createEnhancer();
        if(classLoader ! =null) {
            enhancer.setClassLoader(classLoader);
            if (classLoader instanceof SmartClassLoader &&
                ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                enhancer.setUseCache(false); }}// Set the enhancement target class as the parent class based on the CGLIB pattern of subclasses as enhancement classes
        enhancer.setSuperclass(proxySuperClass);
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

        // Build matching Advice into a Callback collection of Cligb
        Callback[] callbacks = getCallbacks(rootClass);

        // Build the enhanced class
        return createProxyClassAndInstance(enhancer, callbacks);
    }
    catch(Exception e) { ... }}Copy the code

After the enhancement class is built, the BeanPostProcessor replaces the bean in the beanFactory with the implementation of the enhancement class. As for Callback, there are many implementations of AOP, and we can take a look at SpringAop’s generic Callback.

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Object target = null;
    TargetSource targetSource = this.advised.getTargetSource();
    try {
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true; } target = targetSource.getTarget(); Class<? > targetClass = (target ! =null ? target.getClass() : null);
        // Get the Advice list and build it as a call chain
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = methodProxy.invoke(target, argsToUse);
        }
        else {
            // Call the method and its section call chain
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        retVal = processReturnType(proxy, target, method, retVal);
        return retVal;
    }
    finally {
        if(target ! =null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.AopContext.setCurrentProxy(oldProxy); }}}Copy the code

So far, after seeing the instantiation logic and Callback logic of the enhanced class, I believe we have a relatively linear concept of Spring AOP logic, and even say that we can emulate the Spring logic through BeanPostPorcessor and CGLIB also do some simple enhanced implementation. The AOP approach to development allows us to take a horizontal view of all the business of the application, and also gives us more room for execution in the implementation of the business generalization.

The tail

It took a long time to write… Maybe it’s the complexity, maybe it’s laziness, maybe it’s something else, but it took more than two months to finish before 2021. It’s a shame. Seriously re-read the AOP related source code from beginning to end, also found some previously not noticed details, after also want to cooperate with AOP to tidy up the Spring transaction logic, on… Wait until 2021. Because I read it leaning against the source code, there must be some details I didn’t notice, and if there are any misspoken and written messages, I am sure you are welcome to correct myself, crab (world twentyconsciousness). Of course, please come to my blog and check out other shawjie.cn.