一、AOP相關概念
代理模式是 AOP 實現的基礎。在介紹 AOP 的實現步驟之前,先引入 Spring AOP 中的一些概念,接下來我們會用到這些概念。
1.通知(Advice)
通知定義了要織入目標對象的邏輯,以及執行時機。
Spring 中對應了 5 種不同類型的通知:
- 前置通知(Before):在目標方法執行前,執行通知
- 後置通知(After):在目標方法執行後,執行通知,此時不關係目標方法返回的結果是什麼
- 返回通知(After-returning):在目標方法執行後,執行通知
- 異常通知(After-throwing):在目標方法拋出異常後執行通知
- 環繞通知(Around): 目標方法被通知包裹,通知在目標方法執行前和執行後都被會調用
2.切點(Pointcut)
如果說通知定義了在何時執行通知,那麼切點就定義了在何處執行通知。所以切點的作用就是通過匹配規則查找合適的連接點(Joinpoint),AOP 會在這些連接點上織入通知。
3.切面(Aspect)
切面包含了通知和切點,通知和切點共同定義了切面是什麼,在何時,何處執行切面邏輯。
二、AOP實現
簡單 AOP 實現的步驟。這裏 AOP 是基於 JDK 動態代理實現的,只需 3 步即可完成:
- 定義一個包含切面邏輯的對象,這裏假設叫 logMethodInvocation
- 定義一個 Advice 對象(實現了 InvocationHandler 接口),並將上面的logMethodInvocation 和 目標對象傳入
- 將上面的 Adivce 對象和目標對象傳給 JDK 動態代理方法,爲目標對象生成代理
上面步驟比較簡單,不過在實現過程中,還是有一些難度的,這裏要引入一些輔助接口才能實現。接下來就來介紹一下簡單 AOP 的代碼結構:
MethodInvocation 接口 // 實現類包含了切面邏輯
Advice 接口 // 繼承了 InvocationHandler 接口
BeforeAdvice 類 // 實現了 Advice 接口,是一個前置通知
Main // 測試環境
HelloService 接口 // 目標對象接口
HelloServiceImpl // 目標對象
【主業務代碼】
package learn.spring.aop;
// 主業務接口
public interface HelloService {
void sayHelloWorld();
}
------------------------------------------------------
package learn.spring.aop;
// 主業務實現類
public class HelloServiceImpl implements HelloService {
@Override
public void sayHelloWorld() {
System.out.println("hello world !");
}
}
【切面邏輯接口】
package learn.spring.aop;
/**
* 切面邏輯的抽象接口,
* 即在主業務之外的附加業務的抽象接口
* */
public interface MethodInvocation {
void invoke();
}
【整合主業務和附加業務,實現代理】
package learn.spring.aop;
import java.lang.reflect.InvocationHandler;
/**
* 通知抽象接口,繼承代理接口InvocationHandler
* 對於需要在執行主業務之前加通知的類,都需要實現該接口
* */
public interface Advice extends InvocationHandler {
}
--------------------------------------------------
package learn.spring.aop;
import java.lang.reflect.Method;
/**
* 通知邏輯具體實現類,
* 需要在執行之前加通知的對象,作爲構造器參數傳入
* */
public class BeforeAdvice implements Advice {
// 主業務對象
private Object bean;
// 切面邏輯的對象,即在主業務之外的附加業務
private MethodInvocation methodInvocation;
public BeforeAdvice(Object bean, MethodInvocation methodInvocation) {
this.bean = bean;
this.methodInvocation = methodInvocation;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
// 在主業務執行先執行 MethodInvocation中的方法
methodInvocation.invoke();
// 執行主業務方法
return method.invoke(bean,objects);
}
}
【測試環境】
package learn.spring.aop;
import java.lang.reflect.Proxy;
// 測試環境
public class Main {
public static void main(String[] args) {
// 切面邏輯具體實現,這裏使用的是java Lambda語法
MethodInvocation logTask =
() -> System.out.println("log task start");
// 主業務實現
HelloServiceImpl helloService = new HelloServiceImpl();
// 創建切面,確定通知和切點
Advice beforeAdvice = new BeforeAdvice(helloService,logTask);
// 爲目標對象生成代理
HelloService proxy = (HelloService) Proxy.newProxyInstance(
helloService.getClass().getClassLoader(),
helloService.getClass().getInterfaces(),
beforeAdvice);
// 執行方法
proxy.sayHelloWorld();
}
}
【運行結果】
log task start
hello world !