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源碼分析了,希望大家可以繼續關注。