SpringAOP的原始碼解析

bei_er發表於2022-04-17

一、SpringAOP的概念

一、AOP的基本概念

1、連線點(Joinpoint):可以被增強的方法。

2、切點(Pointcut):實際被增強的方法。

3、通知(Advice)(增強):

  3.1.實際增強的邏輯部分叫做通知

  3.2.通知型別包括

  1. 前置通知(執行方法前執行,通常用作引數日誌輸出、許可權校驗等)
  2. 後置通知(邏輯程式碼執行完,準備執行return的程式碼時通知,通常用作執行結果日誌輸出、結果加密等)
  3. 環繞通知(是前置通知和後置通知的綜合,方法執行前和方法執行後都要執行,通常用作方法效能統計、介面耗時、統一加密、解密等)
  4. 異常通知(相當於try{}catch ()中catch執行的部分,程式丟擲異常時執行,通常用作告警處理、事務回滾等)
  5. 最終通知(相當於try{}catch (Exception e){}finally { }中的finally執行的部分,通常用在關閉資源、清理快取等業務邏輯中)

4、切面(Aspect):把通知(增強)應用到切入點的過程。

二、Spring 框架一般都是基於 AspectJ 實現 AOP 操作

(1)AspectJ 不是 Spring 組成部分,獨立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使 用,進行 AOP 操作

三、基於 AspectJ 實現 AOP 操作

(1)基於 xml 配置檔案實現

(2)基於註解方式實現(使用)

二、SpringAOP的使用

1.通過maven方式引用jar包

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.3.17</version>
    </dependency>

2.建立被代理介面和實現類。程式碼如下:

package com.ybe.aop;

public interface  Calculate {
    /**
     * 除法
     * @param numA
     * @param numB
     * @return
     */
    int div(int numA,int numB);
}

package com.ybe.aop.impl;

import com.ybe.aop.Calculate;

public class CalculateImpl implements Calculate {
  
    @Override
    public int div(int numA, int numB) {
        System.out.println("執行目標方法:div");
        return numA / numB;
    }
}

3.使用@Aspect註解建立切面類(Aspect),程式碼如下:

package com.ybe.aop.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Aspect
@Component
public class LogAspectj {

    @Pointcut("execution(* com.ybe.aop.impl.CalculateImpl.*(..))")
    private void pointCut(){
    }

    @Before(value = "pointCut()")
    public void methodBefore(JoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("執行目標方法【"+methodName+"】的<前置通知>,入參"+Arrays.asList(joinPoint.getArgs()));
    }

    @After(value = "pointCut()")
    public void methodAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("執行目標方法【"+methodName+"】的<後置通知>,入參"+Arrays.asList(joinPoint.getArgs()));
    }

    @AfterReturning(value = "pointCut()",returning = "result")
    public void methodReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("執行目標方法【"+methodName+"】的<返回通知>,入參"+Arrays.asList(joinPoint.getArgs()));
    }

    @AfterThrowing(value = "pointCut()")
    public void methodAfterThrowing(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("執行目標方法【"+methodName+"】的<異常通知>,入參"+Arrays.asList(joinPoint.getArgs()));
    }

}

4.建立Config 配置類,使用註解@EnableAspectJAutoProxy開啟aop功能,程式碼如下:

package com.ybe.aop.config;

import com.ybe.aop.Calculate;
import com.ybe.aop.impl.CalculateImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("com.ybe.aop")
@EnableAspectJAutoProxy
public class Config {
    @Bean
    public Calculate calculate(){
        return new CalculateImpl();
    }
}

5.Main的程式碼

 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
 Calculate proxyFactoryBean = context.getBean("calculate", Calculate.class);
 context.div(1,1);

6.執行結果如下:

三、SpringAOP的原始碼分析

SpringAop實現利用了SpringIoc容器。在SpringIOC容器的生命週期過程中整合了SpringAOP的功能。大概過程:通過 @Import註冊 實現了ImportBeanDefinitionRegistrar 介面的 AspectJAutoProxyRegistrar 類。在該類中新增實現了 InstantiationAwareBeanPostProcessor 介面的 AnnotationAwareAspectJAutoProxyCreator 類。在建立AnnoteationConfigApplicationContext的建構函式中會呼叫refresh()方法。refresh方法會進行 AspectJAutoProxyRegistrar 的呼叫,並且生成

AnnotationAwareAspectJAutoProxyCreator 的Bean物件。在第一次呼叫 CreateBean 的時候,進行Advisors的建立。在建立完 Bean後會呼叫AnnotationAwareAspectJAutoProxyCreator的 postProcessAfterInitialization方法。從 Advisors 中查詢是否匹配當前正在建立的Bean。如果能匹配,則建立相關的動態代理物件。

完整原始碼分析分三部分:SpringAOP的初始化、建立動態代理、代理方法呼叫過程。

一、SpringAOP的初始化。

主要邏輯是找到所有標註了 @Aspect 的類,並且解析類中所有的通知方法並新增到 BeanFactoryAspectJAdvisorsBuilder.advisorsCache 快取中。

整體程式碼流程圖如下:

說明:

  1. 建立 AnnotationConfigApplicationContext() 容器。

  2. 在invokeBeanFactoryPostProcessors()中,會呼叫 ConfigurationClassPostProcessor 的 postProcessBeanDefinitionRegistry() 。在此方法中,會找到 @EnableAspectJAutoProxy 的 @Import 屬性傳入的 AspectJAutoProxyRegistrar.class 類。並且執行該類的registerBeanDefinitions() 方法,建立型別為 AnnotationAwareAspectJAutoProxyCreator 、名稱為org.springframework.aop.
    config.internalAutoProxyCreator的 RootBeanDefinition註冊到BeanDefinitionRegistry中。

  3. 在 registerBeanPostProcessors() 中會根據上面一步生成的 RootBeanDefinition物件建立 AnnotationAwareAspectJAutoProxyCreator 的例項。

  4. 在 finishBeanFactoryInitialization() 中第一次執行到 AbstractAutowireCapableBeanFactory.createBean() 時,會執行一段這樣的程式碼,如下

    try {
            // 讓 BeanPostProcessors 有機會返回一個代理而不是目標 bean 例項
            Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
            if (bean != null) {
                return bean;
            }
    }
    
    @Nullable
    protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
    		Object bean = null;
    		if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
    			// Make sure bean class is actually resolved at this point.
    			if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    				Class<?> targetType = determineTargetType(beanName, mbd);
    				if (targetType != null) {
    					bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
    					if (bean != null) {
    						bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
    					}
    				}
    			}
    			mbd.beforeInstantiationResolved = (bean != null);
    		}
    		return bean;
    }
    
    @Nullable
    protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
    	    for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
                 Object result = bp.postProcessBeforeInstantiation(beanClass, beanName);
                 if (result != null) {
                   return result;
                 }
               }
             return null;
    }
    

    以上程式碼會執行 AnnotationAwareAspectJAutoProxyCreator 的 postProcessBeforeInstantiation() 方法。在該方法中會 執行 shouldSkip() 方法。程式碼如下:

    @Override
    protected boolean shouldSkip(Class<?> beanClass, String beanName) {
        // TODO: Consider optimization by caching the list of the aspect names
     // 找到所有候選的 Advisors
     List<Advisor> candidateAdvisors = findCandidateAdvisors();
     for (Advisor advisor : candidateAdvisors) {
         if (advisor instanceof AspectJPointcutAdvisor &&
             ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
             return true;
         }
     }
     return super.shouldSkip(beanClass, beanName);
    }
    

    在 findCandidateAdvisors 中具體會生成所有的 Advisors。

    @Override
    protected List<Advisor> findCandidateAdvisors() {
    		// 找到所有的 實現了 Advisor.class 介面的類,並且生成候選的 Advisors.
    		List<Advisor> advisors = super.findCandidateAdvisors();
    		// 建立所有的帶了 @Aspect 特性的切面類 .
    		if (this.aspectJAdvisorsBuilder != null) {
    			advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    		}
    		return advisors;
    }
    

    aspectJAdvisorsBuilder.buildAspectJAdvisors() 是核心。方法裡面的邏輯如下:

    1.獲取容器裡所有的beanNames.
    2.遍歷 beanNames,根據beanName獲取對應的beanType物件。
    3.判斷beanType是否有@Aspect註解。
    4.如果有,呼叫getAdvisorMethods()通過反射獲取該型別所有的 advisor 的 method 後設資料。
    5.遍歷 methods 呼叫 getAdvisor() 獲取 Advisor 物件(InstantiationModelAwarePointcutAdvisorImpl)
    6.新增到 this.advisorsCache 中。
    

​ postProcessBeforeInstantiation方法會快取所有的advisor,方法的最後返回 null。至此整個 SpringAOP的初始化完成。

二、建立動態代理

​ 在建立Bean的生命週期的 initializeBean 方法中,會執行 AnnotationAwareAspectJAutoProxyCreator的 postProcessAfterInitialization方法。該方法會拿快取BeanFactoryAspectJAdvisorsBuilder.advisorsCache 中所有advisor的pointCut去匹配正在建立的例項Bean的所有方法。如果 advisor 和 Bean 的某一個方法能匹配上,則把該advisor新增到 advisor的候選集合中。直到找出匹配Bean的所有Adsivors。最後根據Adsivor的候選集合和Bean型別建立動態代理物件ProxyFactory。

整體程式碼流程圖如下:

說明:

1.List排序後的順序為:

ExposeInvocationInterceptor

Around

Before

After

AfterReturning

AfterThrowing

2.動態代理的建立

建立動態代理有兩種方法,一種是 JDK ,一種是 CGLib 。

1.如果目標類有實現介面的話,則是使用JDK的方式生成代理物件。

2.配置了使用Cglib進行動態代理或者目標類沒有實現介面,那麼使用Cglib的方式建立代理物件。

三、動態代理呼叫

以 JdkDynamicAopProxy 為例,在呼叫方法的時候會直接呼叫 JdkDynamicAopProxy.invoke()方法,裡面的大概邏輯如下:

1.獲取被代理的實現類;

2.找出所有匹配被呼叫方法的 advisor,並且轉成具體的通知攔截器 MethodInterceptor,返回通知攔截器鏈。轉換程式碼如下:

List<MethodInterceptor> interceptors = new ArrayList<>(3);
// 從Advisor中獲取 Advice
Advice advice = advisor.getAdvice();
// 如果 advice 本身就實現了  MethodInterceptor 介面 ,則直接進行轉換
if (advice instanceof MethodInterceptor) {
    interceptors.add((MethodInterceptor) advice);
}
// AfterReturningAdviceInterceptor MethodBeforeAdviceInterceptor  ThrowsAdviceInterceptor 
// 這三種是通過介面卡的方式進行轉換 MethodInterceptor型別
for (AdvisorAdapter adapter : this.adapters) {
    if (adapter.supportsAdvice(advice)) {
        interceptors.add(adapter.getInterceptor(advisor));
    }
}
if (interceptors.isEmpty()) {
    throw new UnknownAdviceTypeException(advisor.getAdvice());
}
return interceptors.toArray(new MethodInterceptor[0]);

3.建立 ReflectiveMethodInvocation 物件(該物件中包括了 代理物件、被代理物件、執行的方法、方法引數、被代理物件的型別、通知攔截器鏈),執行該物件的proceed()方法,該方法中會進行通知攔截器鏈的遞迴呼叫,具體呼叫流程如下圖。ReflectiveMethodInvocation 物件在通知攔截器鏈呼叫中作用很關鍵,有銜接各個攔截器的作用。

程式碼流程如下圖:

說明:

1.在proceed方法中,會先判斷當前攔截器鏈的索引,如果索引等於最後一個那麼則執行被代理類的方法。

2.如果不是,那麼先獲取該通知攔截器並且執行該攔截器的 proceed 方法(方法接受 ReflectiveMethodInvocation 物件例項),每個通知攔截器中都會呼叫 ReflectiveMethodInvocation 物件例項 的proceed 方法。在這裡會形成遞迴呼叫。

3.通知攔截器的排序請看下圖:

4.五個通知攔截器的程式碼解釋請看上面的程式碼流程圖。

相關文章