前言
之前用十幾篇文章介紹了 Spring IoC 的源碼,作爲與 IoC 齊名的 AOP 自然也不能錯過。同樣的,接下去將會通過幾篇文章來解析 Spring AOP 的源碼。
如何將 Spring 源碼導入 IDEA,請參考:Spring IoC源碼學習:總覽
注:本文的內容以 AspectJ 來進行介紹。
關於 AOP
百度百科:AOP 即 Aspect Oriented Programming,意爲:面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP 是 OOP 的延續,是軟件開發中的一個熱點,也是 Spring 框架中的一個重要內容,是函數式編程的一種衍生範型。利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
例子
我們有兩個接口,一個用於進行加法計算,一個用於進行減法計算,爲了避免計算出現問題,我們需要對每次接口調用的入參進行日誌記錄,於是我們有了以下的第一版實現。
看起來還不錯,簡單明瞭。但是這個方案有個問題,就是後續每次新增一個接口,就需要拷貝一次 “記錄入參” 的代碼。對於一個 “懶人”,這是不可容忍的。好,提出一個公共方法,每個接口都來調用這個方法,於是我們有了以下第二版實現。這裏有點切面的味道了。
這個方案看起來更好了,但是同還是存在問題,雖然不用每次都拷貝代碼了,但是,每個接口總得要調用這個方法吧,有辦法讓 “調用” 也省掉嗎。我們設想一下,我們可以通過策略識別出所有要加入日誌記錄的接口,然後在接口調用時,將日誌記錄注入到接口調用的地方(切點),這就是 AOP 的核心思想。按這個思想,我們有了第三版的實現。
這樣接口只需要關心具體的業務,而不需要關注其他非該接口關注的邏輯或處理。 紅框處,就是面向切面編程的思想。
AOP 的常見概念
通過上面的例子,大家應該對 AOP 有了初步的認識,下面介紹下 AOP 涉及的相關概念。
Joinpoint(連接點):在系統運行之前,AOP 的功能模塊都需要織入到具體的功能模塊中。要進行這種織入過程,我們需要知道在系統的哪些執行點上進行織入過程,這些將要在其之上進行織入操作的系統執行點就稱之爲 Joinpoint,最常見的 Joinpoint 就是方法調用。
Pointcut(切點):用於指定一組 Joinpoint,代表要在這一組 Joinpoint 中織入我們的邏輯,它定義了相應 Advice 將要發生的地方。通常使用正則表達式來表示。對於上面的例子,Pointcut 就是表示 “所有要加入日誌記錄的接口” 的一個 “表達式”。例如:“execution(* com.joonwhee.open.demo.service..*.*(..))”。
Advice(通知/增強):Advice 定義了將會織入到 Joinpoint 的具體邏輯,通過 @Before、@After、@Around 來區別在 JointPoint 之前、之後還是環繞執行的代碼。
Aspect(切面):Aspect 是對系統中的橫切關注點邏輯進行模塊化封裝的 AOP 概念實體。類似於 Java 中的類聲明,在 Aspect 中可以包含多個 Pointcut 以及相關的 Advice 定義。
Weaving(織入):織入指的是將 Advice 連接到 Pointcut 指定的 Joinpoint 處的過程,也稱爲:將 Advice 織入到 Pointcut 指定的 Joinpoint 處。
Target(目標對象):符合 Pointcut 所指定的條件,被織入 Advice 的對象。
對於上面的例子來說:
- “加法接口” 或 “減法接口” 每次被調用時所處的程序執行點都是一個 Jointpoint
- Pointcut 就是用於指定 “加法接口” 和 “減法接口” 的一個 “表達式”,當然這個表達式還可以指定很多其他的接口,表達式常見的格式爲:“execution(* com.joonwhee.open.demo.service..*.*(..))”
- Aspect 是定義 Advice、Pointcut 的地方
- Advice 就是我們要在 “加法接口” 和 “減法接口” 織入的日誌記錄邏輯
- Weaving 就是指將日記記錄邏輯加到 “加法接口” 和 “減法接口” 的過程
- Target 就是定義了 “加法接口” 和 “減法接口” 的對象實例
整體如下圖所示:
簡單的使用
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @author joonwhee
* @date 2019/3/3
*/
@Component
@Aspect
public class AopAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(AopAspect.class);
@Pointcut("execution(* com.joonwhee.open.demo.service..*.*(..))")
public void pointcut() {
}
@Before("pointcut()")
public void before() {
LOGGER.info("before advice");
}
@After("pointcut()")
public void after() {
LOGGER.info("after advice");
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws InterruptedException {
System.out.println("around advice start");
try {
Object result = proceedingJoinPoint.proceed();
System.out.println("result: " + result);
System.out.println("around advice end");
return result;
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
}
實現機制
Spring AOP 底層實現機制目前有兩種:JDK 動態代理、CGLIB 動態字節碼生成。在閱讀源碼前對這兩種機制的使用有個認識,有利於更好的理解源碼。
JDK 動態代理
public class MyInvocationHandler implements InvocationHandler {
private Object origin;
public MyInvocationHandler(Object origin) {
this.origin = origin;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke start");
Object result = method.invoke(origin, args);
System.out.println("invoke end");
return result;
}
}
public class JdkProxyTest {
public static void main(String[] args) {
UserService proxy = (UserService) Proxy.newProxyInstance(JdkProxyTest.class.getClassLoader(),
new Class[]{UserService.class}, new MyInvocationHandler(new UserServiceImpl()));
proxy.doSomething();
}
}
CGLIB 代理
public class CglibInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("intercept start");
Object result = proxy.invokeSuper(obj, args);
System.out.println("intercept end");
return result;
}
}
public class CglibProxyTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(CglibObject.class);
enhancer.setCallback(new CglibInterceptor());
CglibObject proxy = (CglibObject) enhancer.create();
proxy.doSomething();
}
}