Spring AOP(一) AOP基本概念

 Spring框架自誕生之日就拯救我等程序員於水火之中,它有兩大法寶,一個是IoC控制反轉,另一個便是AOP面向切面編程。今日我們就來破一下它的AOP法寶,以便以後也能自由使出一手AOP大法。

 AOP全名Aspect-oriented programming面向切面編程大法,它有很多兄弟,分別是經常見的面向對象編程,樸素的面向過程編程和神祕的函數式編程等。所謂AOP的具體解釋,以及和OOP的區別不清楚的同學可以自行去了解。

 AOP實現的關鍵在於AOP框架自動創建的AOP代理,AOP代理主要分爲靜態代理和動態代理。本文就主要講解AOP的基本術語,然後用一個例子讓大家徹底搞懂這些名詞,最後介紹一下AOP的兩種代理方式:

  • 以AspectJ爲代表的靜態代理。
  • 以Spring AOP爲代表的動態代理。

基本術語

(1)切面(Aspect)

 切面是一個橫切關注點的模塊化,一個切面能夠包含同一個類型的不同增強方法,比如說事務處理和日誌處理可以理解爲兩個切面。切面由切入點和通知組成,它既包含了橫切邏輯的定義,也包括了切入點的定義。 Spring AOP就是負責實施切面的框架,它將切面所定義的橫切邏輯織入到切面所指定的連接點中。

@Component
@Aspect
public class LogAspect {
}

可以簡單地認爲, 使用 @Aspect 註解的類就是切面

(2) 目標對象(Target)

 目標對象指將要被增強的對象,即包含主業務邏輯的類對象。或者說是被一個或者多個切面所通知的對象。

(3) 連接點(JoinPoint)

 程序執行過程中明確的點,如方法的調用或特定的異常被拋出。連接點由兩個信息確定:

  • 方法(表示程序執行點,即在哪個目標方法)
  • 相對點(表示方位,即目標方法的什麼位置,比如調用前,後等)

 簡單來說,連接點就是被攔截到的程序執行點,因爲Spring只支持方法類型的連接點,所以在Spring中連接點就是被攔截到的方法。

@Before("pointcut()")
public void log(JoinPoint joinPoint) { //這個JoinPoint參數就是連接點
}

(4) 切入點(PointCut)

 切入點是對連接點進行攔截的條件定義。切入點表達式如何和連接點匹配是AOP的核心,Spring缺省使用AspectJ切入點語法。 
 一般認爲,所有的方法都可以認爲是連接點,但是我們並不希望在所有的方法上都添加通知,而切入點的作用就是提供一組規則(使用 AspectJ pointcut expression language 來描述) 來匹配連接點,給滿足規則的連接點添加通知。

@Pointcut("execution(* com.remcarpediem.test.aop.service..*(..))")
public void pointcut() {
}

 上邊切入點的匹配規則是com.remcarpediem.test.aop.service包下的所有類的所有函數。

(5) 通知(Advice)

 通知是指攔截到連接點之後要執行的代碼,包括了“around”、“before”和“after”等不同類型的通知。Spring AOP框架以攔截器來實現通知模型,並維護一個以連接點爲中心的攔截器鏈。 

// @Before說明這是一個前置通知,log函數中是要前置執行的代碼,JoinPoint是連接點,
@Before("pointcut()")
public void log(JoinPoint joinPoint) { 
}

(6) 織入(Weaving)

 織入是將切面和業務邏輯對象連接起來, 並創建通知代理的過程。織入可以在編譯時,類加載時和運行時完成。在編譯時進行織入就是靜態代理,而在運行時進行織入則是動態代理。

(7) 增強器(Adviser)

 Advisor是切面的另外一種實現,能夠將通知以更爲複雜的方式織入到目標對象中,是將通知包裝爲更復雜切面的裝配器。Advisor由切入點和Advice組成。
 Advisor這個概念來自於Spring對AOP的支撐,在AspectJ中是沒有等價的概念的。Advisor就像是一個小的自包含的切面,這個切面只有一個通知。切面自身通過一個Bean表示,並且必須實現一個默認接口。

// AbstractPointcutAdvisor是默認接口
public class LogAdvisor extends AbstractPointcutAdvisor {
    private Advice advice; // Advice
    private Pointcut pointcut; // 切入點

    @PostConstruct
    public void init() {
        // AnnotationMatchingPointcut是依據修飾類和方法的註解進行攔截的切入點。
        this.pointcut = new AnnotationMatchingPointcut((Class) null, Log.class);
        // 通知
        this.advice = new LogMethodInterceptor();
    }
}

深入理解

 看完了上面的理論部分知識, 我相信還是會有不少朋友感覺AOP 的概念還是很模糊, 對 AOP 的術語理解的還不是很透徹。現在我們就找一個具體的案例來說明一下。
 簡單來講,整個 aspect 可以描述爲: 滿足 pointcut 規則的 joinpoint 會被添加相應的 advice 操作。我們來看下邊這個例子。

@Component
@Aspect // 切面
public class LogAspect {
    private final static Logger LOGGER = LoggerFactory.getLogger(LogAspect.class.getName());
     // 切入點,表達式是指com.remcarpediem.test.aop.service
     // 包下的所有類的所有方法
    @Pointcut("execution(* com.remcarpediem.test.aop.service..*(..))")
    public void aspect() {}
    // 通知,在符合aspect切入點的方法前插入如下代碼,並且將連接點作爲參數傳遞
    @Before("aspect()")
    public void log(JoinPoint joinPoint) { //連接點作爲參數傳入
        if (LOGGER.isInfoEnabled()) {
            // 獲得類名,方法名,參數和參數名稱。
            Signature signature = joinPoint.getSignature();
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            Object[] arguments = joinPoint.getArgs();
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

            String[] argumentNames = methodSignature.getParameterNames();

            StringBuilder sb = new StringBuilder(className + "." + methodName + "(");

            for (int i = 0; i< arguments.length; i++) {
                Object argument = arguments[i];
                sb.append(argumentNames[i] + "->");
                sb.append(argument != null ? argument.toString() : "null ");
            }
            sb.append(")");

            LOGGER.info(sb.toString());
        }
    }
}

 上邊這段代碼是一個簡單的日誌相關的切面,依次定義了切入點和通知,而連接點作爲log的參數傳入進來,進行一定的操作,比如說獲取連接點函數的名稱,參數等。

靜態代理模式

 所謂靜態代理就是AOP框架會在編譯階段生成AOP代理類,因此也稱爲編譯時增強。ApsectJ是靜態代理的實現之一,也是最爲流行的。靜態代理由於在編譯時就生成了代理類,效率相比動態代理要高一些。AspectJ可以單獨使用,也可以和Spring結合使用。

動態代理模式

 與靜態代理不同,動態代理就是說AOP框架不會去修改編譯時生成的字節碼,而是在運行時在內存中生成一個AOP代理對象,這個AOP對象包含了目標對象的全部方法,並且在特定的切點做了增強處理,並回調原對象的方法。

 Spring AOP中的動態代理主要有兩種方式:JDK動態代理和CGLIB動態代理。

 JDK代理通過反射來處理被代理的類,並且要求被代理類必須實現一個接口。核心類是 InvocationHandler接口 和 Proxy類。
 而當目標類沒有實現接口時,Spring AOP框架會使用CGLIB來動態代理目標類。
 CGLIB(Code Generation Library),是一個代碼生成的類庫,可以在運行時動態的生成某個類的子類。CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記爲final,那麼它是無法使用CGLIB做動態代理的。核心類是 MethodInterceptor 接口和Enhancer 類

兩種動態代理的區別

後記

 AOP的基礎知識都比較枯燥,本人也不擅長概念性的文章,不過下一篇文章就是AOP源碼分析了,希望大家可以繼續關注。

clipboard.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章