AOP(Aspect Orient Programming),作為面向對象程式設計的一種補充,廣泛應用于處理一些具有橫切性質的系統級服務,如事務管理、安全檢查、緩存、對象池管理等。
AOP 實作的關鍵就在于 AOP 架構自動建立的 AOP 代理,AOP 代理則可分為靜态代理和動态代理兩大類,其中靜态代理是指使用 AOP 架構提供的指令進行編譯,進而在編譯階段就可生成 AOP 代理類,是以也稱為編譯時增強;而動态代理則在運作時借助于 JDK 動态代理、CGLIB 等在記憶體中“臨時”生成 AOP 動态代理類,是以也被稱為運作時增強。
先說說AspectJ
在今天之前,我還以為AspectJ 是Spring的一部分,因為我們談到Spring AOP一般都會提到AspectJ。原來AspectJ是一套獨立的面向切面程式設計的解決方案。
下面我們抛開Spring,單純的看看AspectJ。
1、AspectJ 安裝
下載下傳AspectJ jar包,然後輕按兩下安裝。安裝好的目錄結構為:
bin:存放了 aj、aj5、ajc、ajdoc、ajbrowser 等指令,其中 ajc 指令最常用,它的作用類似于 javac
doc:存放了 AspectJ 的使用說明、參考手冊、API 文檔等文檔
lib:該路徑下的 4 個 JAR 檔案是 AspectJ 的核心類庫
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("記錄日志 ...");
}
}
3、編譯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 架構需要每次運作時都進行動态增強。
再談 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 沒有任何差別,一樣使用 元素進行配置,一樣支援使用依賴注入來配置屬性值。
使用Spring AOP的改寫 Hello World的例子。
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
記錄日志...
總結
AOP 代理 = 原來的業務類+增強處理。
這個生成AOP 代理由 Spring 的 IoC 容器負責生成。也由 IoC 容器負責管理。是以,AOP 代理可以直接使用容器中的其他 Bean 執行個體作為目标,這種關系可由 IoC 容器的依賴注入提供。回顧Hello World的例子,其中程式員參
與的隻有 3 個部分:
定義普通業務元件。
定義切入點,一個切入點可能橫切多個業務元件。
定義增強處理,增強處理就是在 AOP 架構為普通業務元件織入的處理動作。
最後說說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 在實作上幾乎無關。