歡迎光臨
每天分享高質量文章

Spring AOP,AspectJ,CGLIB 有點暈

點擊上方“芋道原始碼”,選擇“置頂公眾號”

技術文章第一時間送達!

原始碼精品專欄

 

來源:http://t.cn/E7KtEEy


AOP(Aspect Orient Programming),作為面向物件編程的一種補充,廣泛應用於處理一些具有橫切性質的系統級服務,如事務管理、安全檢查、快取、物件池管理等。AOP 實現的關鍵就在於 AOP 框架自動創建的 AOP 代理,AOP 代理則可分為靜態代理和動態代理兩大類,其中靜態代理是指使用 AOP 框架提供的命令進行編譯,從而在編譯階段就可生成 AOP 代理類,因此也稱為編譯時增強;而動態代理則在運行時借助於 JDK 動態代理、CGLIB 等在記憶體中“臨時”生成 AOP 動態代理類,因此也被稱為運行時增強。

1. 先說說AspectJ

在今天之前,我還以為AspectJ 是Spring的一部分,因為我們談到Spring AOP一般都會提到AspectJ。原來AspectJ是一套獨立的面向切麵編程的解決方案。下麵我們拋開Spring,單純的看看AspectJ。

1.1 AspectJ 安裝

AspectJ 下載地址(http://www.eclipse.org/aspectj/downloads.php)。

下載AspectJ jar包,然後雙擊安裝。安裝好的目錄結構為:

  • bin:存放了 aj、aj5、ajc、ajdoc、ajbrowser 等命令,其中 ajc 命令最常用,它的作用類似於 javac

  • doc:存放了 AspectJ 的使用說明、參考手冊、API 文件等文件

  • lib:該路徑下的 4 個 JAR 檔案是 AspectJ 的核心類庫

1.2 AspectJ HelloWorld 實現

// 業務組件  SayHelloService

package com.ywsc.fenfenzhong.aspectj.learn;
public class SayHelloService {
    public void say(){
        System.out.print("Hello  AspectJ");
    }
}

需要來了,在需要在呼叫say()方法之後,需要記錄日誌。那就是通過AspectJ的後置增強吧。

LogAspect 日誌記錄組件,實現對com.ywsc.fenfenzhong.aspectj.learn.SayHelloService 後置增強

package com.ywsc.fenfenzhong.aspectj.learn;
public aspect LogAspect {
    pointcut logPointcut():execution(void SayHelloService.say());
    after():logPointcut(){
         System.out.println("記錄日誌 ...");
    }
}

編譯 SayHelloService:

執行命令   ajc -d . SayHelloService.java LogAspect.java
生成 SayHelloService.class
執行命令    java SayHelloService
輸出  Hello AspectJ  記錄日誌
  • ajc.exe 可以理解為 javac.exe 命令,都用於編譯 Java 程式,區別是 ajc.exe 命令可識別 AspectJ 的語法;我們可以將 ajc.exe 當成一個增強版的 javac.exe 命令.執行ajc命令後的 SayHelloService.class 檔案不是由原來的 SayHelloService.java 檔案編譯得到的,該 SayHelloService.class 里新增了打印日誌的內容——這表明 AspectJ 在編譯時“自動”編譯得到了一個新類,這個新類增強了原有的 SayHelloService.java 類的功能,因此 AspectJ 通常被稱為編譯時增強的 AOP 框架。

與 AspectJ 相對的還有另外一種 AOP 框架,它不需要在編譯時對標的類進行增強,而是運行時生成標的類的代理類,該代理類要麼與標的類實現相同的接口,要麼是標的類的子類——總之,代理類的實體可作為標的類的實體來使用。一般來說,編譯時增強的 AOP 框架在性能上更有優勢——因為運行時動態增強的 AOP 框架需要每次運行時都進行動態增強。

2. 再談 Spring AOP

Spring AOP也是對標的類增強,生成代理類。但是與AspectJ的最大區別在於—Spring AOP的運行時增強,而AspectJ是編譯時增強。

曾經以為AspectJ是Spring AOP一部分,是因為Spring AOP使用了AspectJ的Annotation。使用了Aspect來定義切麵,使用Pointcut來定義切入點,使用Advice來定義增強處理。雖然使用了Aspect的Annotation,但是並沒有使用它的編譯器和織入器。其實現原理是JDK 動態代理,在運行時生成代理類。

為了啟用 Spring 對 @AspectJ 方面配置的支持,並保證 Spring 容器中的標的 Bean 被一個或多個方面自動增強,必須在 Spring 配置檔案中添加如下配置

<aop:aspectj-autoproxy/>

當啟動了 @AspectJ 支持後,在 Spring 容器中配置一個帶 @Aspect 註釋的 Bean,Spring 將會自動識別該 Bean,並將該 Bean 作為方面 Bean 處理。方面Bean與普通 Bean 沒有任何區別,一樣使用  元素進行配置,一樣支持使用依賴註入來配置屬性值。

2.1 Spring AOP HelloWorld 實現

使用Spring AOP的改寫 Hello World的例子。

// 業務組件  SayHelloService

package com.ywsc.fenfenzhong.aspectj.learn;
import org.springframework.stereotype.Component;
@Component
public class SayHelloService {
    public void say(){
        System.out.print("Hello  AspectJ");
    }
}

做後置增強的日誌處理。

package com.ywsc.fenfenzhong.aspectj.learn;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LogAspect {
     @After("execution(* com.ywsc.fenfenzhong.aspectj.learn.SayHelloService.*(..))")
     public void log(){
         System.out.println("記錄日誌 ...");
     }
}
package com.ywsc.fenfenzhong.mongodb;
import com.ywsc.fenfenzhong.aspectj.learn.SayHelloService;
public class TestCase {
    public static void main(String[] args) {
        SayHelloService sayHelloService = ApplicationUtil.getContext().getBean(SayHelloService.class);
        sayHelloService.say();
    }
}

輸出結果

Hello  AspectJ

記錄日誌…

2.2 Hello World 後的總結

AOP 代理 = 原來的業務類+增強處理。

這個生成AOP 代理由 Spring 的 IoC 容器負責生成。也由 IoC 容器負責管理。因此,AOP 代理可以直接使用容器中的其他 Bean 實體作為標的,這種關係可由 IoC 容器的依賴註入提供。回顧Hello World的例子,其中程式員參與的只有 3 個部分:

  • 定義普通業務組件。

  • 定義切入點,一個切入點可能橫切多個業務組件。

  • 定義增強處理,增強處理就是在 AOP 框架為普通業務組件織入的處理動作。

3. 最後說說CGLIB

CGLIB(Code Generation Library)它是一個代碼生成類庫。它可以在運行時候動態是生成某個類的子類。代理樣式為要訪問的標的物件提供了一種途徑,當訪問物件時,它引入了一個間接的層。JDK自從1.3版本開始,就引入了動態代理,並且經常被用來動態地創建代理。JDK的動態代理用起來非常簡單,唯一限制便是使用動態代理的物件必須實現一個或多個接口。而CGLIB缺不必有此限制。要想Spring AOP 通過CGLIB生成代理,只需要在Spring 的配置檔案引入

<aop:aspectj-autoproxy proxy-target-class="true"/>

CGLIB包的底層是通過使用一個小而快的位元組碼處理框架ASM(Java位元組碼操控框架),來轉換位元組碼並生成新的類。由於沒有瞭解過class 檔案和位元組碼,因而也就寫不下去了。

也許學習下來最大的收穫便是 弄清楚了 AspectJ 和 Spring AOP 在實現上幾乎無關。



如果你對 Dubbo / Netty 等等原始碼與原理感興趣,歡迎加入我的知識星球一起交流。長按下方二維碼噢

目前在知識星球更新了《Dubbo 原始碼解析》目錄如下:

01. 除錯環境搭建
02. 專案結構一覽
03. 配置 Configuration
04. 核心流程一覽

05. 拓展機制 SPI

06. 執行緒池

07. 服務暴露 Export

08. 服務取用 Refer

09. 註冊中心 Registry

10. 動態編譯 Compile

11. 動態代理 Proxy

12. 服務呼叫 Invoke

13. 呼叫特性 

14. 過濾器 Filter

15. NIO 服務器

16. P2P 服務器

17. HTTP 服務器

18. 序列化 Serialization

19. 集群容錯 Cluster

20. 優雅停機

21. 日誌適配

22. 狀態檢查

23. 監控中心 Monitor

24. 管理中心 Admin

25. 運維命令 QOS

26. 鏈路追蹤 Tracing

… 一共 69+ 篇

目前在知識星球更新了《Netty 原始碼解析》目錄如下:

01. 除錯環境搭建
02. NIO 基礎
03. Netty 簡介
04. 啟動 Bootstrap

05. 事件輪詢 EventLoop

06. 通道管道 ChannelPipeline

07. 通道 Channel

08. 位元組緩衝區 ByteBuf

09. 通道處理器 ChannelHandler

10. 編解碼 Codec

11. 工具類 Util

… 一共 61+ 篇

目前在知識星球更新了《資料庫物體設計》目錄如下:


01. 商品模塊
02. 交易模塊
03. 營銷模塊
04. 公用模塊

… 一共 17+ 篇


目前在知識星球更新了《Spring 原始碼解析》目錄如下:


01. 除錯環境搭建
02. IoC Resource 定位
03. IoC BeanDefinition 載入

04. IoC BeanDefinition 註冊

05. IoC Bean 獲取

06. IoC Bean 生命周期

… 一共 35+ 篇


原始碼不易↓↓↓

點贊支持老艿艿↓↓

赞(0)

分享創造快樂