要想了解Spring的AOP的實現,首先必須要瞭解代理模式。
如果只是簡單代理一個對象,那麼我們自己就可以很好的實現。
聲明一個抽象主題,支付接口:
public interface ToCPayment {
void pay();
}
被代理的實現類
public class ToCPaymentImpl implements ToCPayment {
@Override
public void pay() {
System.out.println("以用戶的名義進行支付");
}
}
代理角色(持有被代理角色)
public class AliToC implements ToCPayment {
private ToCPayment toCPayment;
public AliToC(ToCPayment toCPayment){
this.toCPayment = toCPayment;
}
@Override
public void pay() {
beforePay();
toCPayment.pay();
afterPay();
}
private void afterPay() {
System.out.println("支付給收錢方");
}
private void beforePay() {
System.out.println("從銀行卡取款");
}
}
這樣在調用接口的時候,直接就可以調用代理對象。
public class ProxyDemo {
public static void main(String[] args) {
ToCPayment toCProxy = new AliToC( new ToCPaymentImpl());
toCProxy.pay();
}
}
,但是這樣的實現有一個弊端,以上只是針對ToC的代理,如果此時增加一個ToB豈不是又要自己手寫一個被代理的類?這種靜態代理的方式無疑在代理多的情況下維護成本指數級別增加。
JDK的動態代理
上面的思路是沒有問題的。只是相關的實現方式需要改進。
我們知道,類是通過類加載器加載的。
1.通過findClass獲取到二進制字節流。
2.根據讀取到的字節流,將其代表的靜態數據結構轉換成方法區運行時的數據結構。有了數據結構,才能讓外界通過class對象取訪問。
3.生成一個代表該類的class對象,作爲方法區該類的數據訪問入口。
改進的切入點
根據一定的規則取改動或者生成新的字節流,將切面邏輯織入進去。
這就是動態代理,在程序運行時動態生成類的字節碼,並加載到JVM中。
她要求被代理的類必須實現接口(唯一約束),否則不能使用JDK動態代理。
其中關鍵的有一個核心類和一個接口。
public class Proxy implements java.io.Serializable
public interface InvocationHandler
只有實現了InvocationHandler的類,才具有代理功能。相關的切面邏輯就存在於此接口的實現類裏面。此接口只有一個方法。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
當我們銅鼓動態代理對象調用一個方法的時候 ,這個方法的調用就會被轉發到實現了這個接口的本方法裏面。
此方法接受三個參數。
//Object proxy 代理對象(不是被代理對象)
// Method method 調用的目標對象的方法示例
//方法的參數
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
Proxy類
創建代理類。
裏面有一個很重要的方法
//ClassLoader loader 聲明哪個加載器來加載class\
//爲代理對象要加上的接口
// InvocationHandler 用來 表示動態代理的方法會關聯到哪個 InvocationHandler 的實現類
//返回的時一個被改造的類 但是 繼承自這些接口 所以可以偷天換日
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
JDK動態代理例子
下載我們將增強邏輯直接寫在InvocationHandler的實現類裏面。
public class AliPayInvocationHandler implements InvocationHandler {
//用來保存被代理的對象
private Object targetObject;
public AliPayInvocationHandler(Object targetObject){
this.targetObject = targetObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforePay();
//目標類 方法參
Object result = method.invoke(targetObject, args);
afterPay();
return result;
}
//增強邏輯
private void afterPay() {
System.out.println("支付給收錢方");
}
//增強邏輯
private void beforePay() {
System.out.println("從銀行卡取款");
}
}
public class JdkUtil {
public static <T> T newProxyInstance(T targetObject, InvocationHandler handler){
ClassLoader classLoader = targetObject.getClass().getClassLoader();
Class<?>[] interfaces = targetObject.getClass().getInterfaces();
return (T) Proxy.newProxyInstance(classLoader,interfaces,handler);
}
}
public class ProxyDemo {
public static void main(String[] args) {
//被代理的對象
ToCPayment toCPayment = new ToCPaymentImpl();
AliPayInvocationHandler handler = new AliPayInvocationHandler(toCPayment);
ToCPayment payment = JdkUtil.newProxyInstance(toCPayment, handler);
payment.pay();
}
}
運行:
通過動態代理,可以複用增強的邏輯 ,代理對象是自動生成的,不用顯式的去寫一個類。
CGLIB
JDK動態代理要求對象必須實現接口,否則不能使用動態代理。這樣在於在接口調用方便偷天換日。
CGLIB全程 Code Generation Libary.不要求目標必須實現接口,內部封裝了ASM字節碼操作框架。
缺點在於:上手難度高 ,對JVM不熟悉直接修改字節碼,會發生莫名其妙的問題。
簡單來說CGLIB只是動態生成目標類的子類,覆蓋非final的方法,綁定鉤子回調自定義攔截器 。
使用cglib先導包。
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.9</version>
</dependency>
主要由三個部分組成。
CallBack接口
```clike
public interface Callback {
}
`一個空接口,沒有方法,僅僅是標誌作用。
MethodInterceptor接口
public interface MethodInterceptor extends Callback {
//參數1 動態代理對象
//被代理的方法
// 方法參數
//代理方法
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}
我們可以通過實現這個接口,構造出通用的邏輯。
Enhancer類
幫助我們創建出動態代理實例出來。
CgLib使用示例
定義通用切面
public class AliPayMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
beforePay();
//執行被代理方法
Object invoke = methodProxy.invokeSuper(o, objects);
afterPay();
return invoke;
}
private void afterPay() {
System.out.println("支付給收錢方");
}
private void beforePay() {
System.out.println("從銀行卡取款");
}
}
創建動態代理類
public class CgLibUtil {
public static <T> T createProxy(T targetObject , MethodInterceptor interceptor){
return (T) Enhancer.create(targetObject.getClass(), interceptor);
}
}
創建被代理對象,(無接口)
public class CommonPay {
public void pay(){
System.out.println("一個通用的支付通道");
}
}
Demo
public class CgLibDemo {
public static void main(String[] args) {
//無接口的類
CommonPay commonPay = new CommonPay();
AliPayMethodInterceptor interceptor = new AliPayMethodInterceptor();
CommonPay commonPayProxy = CgLibUtil.createProxy(commonPay,interceptor);
commonPayProxy.pay();
}
}
運行結果
CGLIB方式 不僅僅支持無接口的對象代理,也支持有接口的對象代理。
public class CgLibDemo {
public static void main(String[] args) {
/* CommonPay commonPay = new CommonPay();
AliPayMethodInterceptor interceptor = new AliPayMethodInterceptor();
CommonPay commonPayProxy = CgLibUtil.createProxy(commonPay,interceptor);
commonPayProxy.pay();*/
//tocPayMent接口的實現類
ToCPayment toCPayment = new ToCPaymentImpl();
AliPayMethodInterceptor interceptor = new AliPayMethodInterceptor();
ToCPayment proxy = CgLibUtil.createProxy(toCPayment, interceptor);
proxy.pay();
}
}
但是JDK動態代理模式,如果強行代理無接口的類,就會拋出異常。
public class CgLibDemo {
public static void main(String[] args) {
/* CommonPay commonPay = new CommonPay();
AliPayMethodInterceptor interceptor = new AliPayMethodInterceptor();
CommonPay commonPayProxy = CgLibUtil.createProxy(commonPay,interceptor);
commonPayProxy.pay();*/
//tocPayMent的實現類
/* ToCPayment toCPayment = new ToCPaymentImpl();
AliPayMethodInterceptor interceptor = new AliPayMethodInterceptor();
ToCPayment proxy = CgLibUtil.createProxy(toCPayment, interceptor);
proxy.pay();*/
//嘗試使用JDK動態代理
CommonPay commonPay = new CommonPay();
AliPayInvocationHandler handler = new AliPayInvocationHandler(commonPay);
CommonPay newProxyInstance = JdkUtil.newProxyInstance(commonPay, handler);
newProxyInstance.pay();
}
}