spring學習筆記(6)AOP前夕[1]jdk動態代理實例解析


JDK動態代理技術

動態代理最常見應用是AOP(面向切面編程)。通過AOP,我們能夠地拿到我們的程序運行到某個節點時的方法、對象、入參、返回參數,並動態地在方法調用前后新添一些新的方法邏輯,來滿足我們的新需求,比如日志記錄等。
動態代理常見有兩種方式:基於JDK的反射技術的動態代理和基於CGLib的動態代理。

使用反射技術創建動態代理

JDK創建動態代理的核心是java.lang.reflect.InvocationHandler接口和java.lang.reflect.Proxy類。讓我們先分析需求,拿出模型示例,再依據示例來進行講解這兩個核心接口/類的用法。

需求分析:

面對一個大型項目,里面的類可能已設計得非常龐大臃腫,一個類里可能有上十個方法,現在,我們需要為對每個方法進行性能監控。統計方法的運行時間。如果我們通過直接在設計好的每個類方法開始結束記錄時間戳來計算方法運行耗時,會有如下缺點:
1. 我們的日志記錄是侵入式,同時還嵌入了大量重復冗雜的代碼,如果日后需要修改,則要針對每個方法修改一遍,既不符合開放封閉的設計原則,同時也不便維護還容易出錯。
2. 從業務邏輯角度來看,這些性能統計的代碼和我們既有類實現的業務功能沒有任何關系,如果把它們整合在一起,會造成兩個不相關功能之間的耦合,不符合職責分明的原則。
那么,有沒辦法既不修改我們的原有類,同時又能增強我們的類功能呢?比如這里為我們的類每個方法都添加性能監控?答案便是使用動態代理。

實例展示

1. 定義我們的代理接口

package com.proxy.demo1;

public interface MyProxy {

void method1() throws InterruptedException;
void method2() throws InterruptedException;
void method3() throws InterruptedException;
}

2. 定義我們的被代理對象——龐大臃腫的“老類”

package com.proxy.demo1;
//這是我們項目中“歷史悠久”的類,功能完整,有很多方法。現在我們需要為每個方法都實現性能統計
//我們的被代理類要實現我們的代理接口,總某種程度講,這也是侵入式的。但是最微弱的侵入。
public class OldClass implements MyProxy {
@Override
public void method1() throws InterruptedException{
System.out.println("正在處理業務邏輯1");
Thread.sleep(100);//模擬處理業務邏輯4過程
System.out.println("業務邏輯1處理完成");
}
@Override
public void method2() throws InterruptedException{
System.out.println("正在處理業務邏輯2");
Thread.sleep(200);//模擬處理業務邏輯2過程
System.out.println("業務邏輯2處理完成");
}
@Override
public void method3() throws InterruptedException{
System.out.println("正在處理業務邏輯3");
Thread.sleep(300);//模擬處理業務邏輯3過程
System.out.println("業務邏輯3處理完成");
}
//下面還有很多很多方法。。
}

3. 定義我們的invokationHandler

package com.proxy.demo1;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//這里我們使用了泛型,假設我們有很多的類都需要進行性能監控,就可以通過在創建本類對象時在泛型標識處改成對應需要監控的類即可。
//注意需要實現JDK反射包中的InvocationHandler接口
public class //這里我們使用了泛型,假設我們有很多的類都需要進行性能監控,就可以通過在創建本類對象時在泛型標識處改成對應需要監控的類即可。
//注意需要實現JDK反射包中的InvocationHandler接口<E> implements InvocationHandler {

//需要被代理的對象
private E target;

public MyInvokationHandler(E target){
this.target = target;
}
/**
* @param
* proxy : 在其上調用方法的代理實例
* method : 對應於在目標對象調用的方法。
* args : 包含傳入代理實例上方法調用的參數值的對象數組,如果接口方法不使用參數,則為 null
* 基本類型的參數被包裝在適當基本包裝器類(如 java.lang.Integer 或 java.lang.Boolean)的實例中。
* @return 從代理實例的方法調用返回的值。如果接口方法的聲明返回類型是基本類型,
* 則此方法返回的值一定是相應基本包裝對象類的實例;否則,它一定是可分配到聲明返回類型的類型。
* 如果此方法返回的值為 null 並且接口方法的返回類型是基本類型,則代理實例上的方法調用將拋出 NullPointerException
* 否則,如果此方法返回的值與上述接口方法的聲明返回類型不兼容,則代理實例上的方法調用將拋出 ClassCastException。
* @throws Throwable - 從代理實例上的方法調用拋出的異常。
* 該異常的類型必須可以分配到在接口方法的 throws 子句中聲明的任一異常類型
* 或未經檢查的異常類型 java.lang.RuntimeException 或 java.lang.Error。
* 如果此方法拋出經過檢查的異常,該異常不可分配到在接口方法的 throws 子句中聲明的任一異常類型.
* 代理實例的方法調用將拋出包含此方法曾拋出的異常的 UndeclaredThrowableException。
*/

@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Long beginTime = System.currentTimeMillis();//記錄開始時間
//調用目標對象的方法,同時獲取該方法的返回值,作為我們本代理方法(invoke)的返回值
Object returnValue = method.invoke(target, args);//target為我們方法所在的目標類,args為方法參數
System.out.println("方法" + method.getName() + "調用結束,耗時"+ (System.currentTimeMillis() - beginTime));
return returnValue;
}

}

從字面意思理解是InvocationHandler是調用處理器,在這里,它是一個方法調用處理器。更通俗來說,我們可以將它理解成一個攔截器,當我們調用被代理類中的方法時,就會被MyInvocationHandler攔截下來,再調用我們的invoke方法。

4. 測試方法

package com.proxy.demo1;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class MainTest {
public static void main(String args[]) throws InterruptedException{
//新建我們的被代理對象
OldClass oldClass = new OldClass();
//創建我們的“攔截器”,並注入被代理對象
InvocationHandler handler = new MyInvocationHandler<OldClass>(oldClass);
/*getProxy返回一個指定接口的代理類實例,
該接口可以將方法調用指派到指定的調用處理程序,這里是我們自定義的handler
第一個參數 - 定義代理類的類加載器
第二個參數 - 代理類要實現的接口列表
第三個參數- 指派方法調用的調用處理程序 InvocationHandler
*/

MyProxy myProxy = (MyProxy)Proxy.newProxyInstance(MyProxy.class.getClassLoader(),new Class[]{MyProxy.class},handler);

myProxy.method1();
myProxy.method2();
myProxy.method3();
}
}

5. 打印結果

正在處理業務邏輯1
業務邏輯1處理完成
方法method1調用結束,耗時101
正在處理業務邏輯2
業務邏輯2處理完成
方法method2調用結束,耗時200
正在處理業務邏輯3
業務邏輯3處理完成
方法method3調用結束,耗時301

我們通過代理類來調用OldClass的方法,實現了對OldClass類中所有方法耗時統計的性能監控功能,但我們並未在OldClass中嵌入任何相關的業務邏輯代碼,唯一的修改就是實現了我們的代理接口。

實例分析

我們方法調用的核心實現在於使用invocationHandler,實際上,我們在通過代理接口調用被代理對象的方法如myProxy.method1()的時候,我們實際調用的是我們自定義的handler里面的invoke方法,只是,我們在invoke方法又根據傳入對象(oldClass)和參數(這里沒有傳參),重新調用了我們oldClass里面的method1而已。而且通過這種動態代理,我們還需要修改我們的上層接口,比如我是在oldClassBoss中調用oldClass的method1方法的,現在要在我們的oldClassBoss中創建代理並通過myProxy.method1();來實現我們對原發的性能監控增強功能。這是我們需要明確的。

源碼下載

本篇文章的實例源碼如果需要請到https://github.com/jeanhao/spring的jdkProxy文件夾下載


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com