詳解 Java 中的三種代理模式

Java小鋪發表於2018-08-23

代理模式

代理(Proxy)是一種設計模式,提供了對目標物件另外的訪問方式;即通過代理物件訪問目標物件.這樣做的好處是:可以在目標物件實現的基礎上,增強額外的功能操作,即擴充套件目標物件的功能.

這裡使用到程式設計中的一個思想:不要隨意去修改別人已經寫好的程式碼或者方法,如果需改修改,可以通過代理的方式來擴充套件該方法.

舉個例子來說明代理的作用:假設我們想邀請一位明星,那麼並不是直接連線明星,而是聯絡明星的經紀人,來達到同樣的目的.明星就是一個目標物件,他只要負責活動中的節目,而其他瑣碎的事情就交給他的代理人(經紀人)來解決.這就是代理思想在現實中的一個例子.

用圖表示如下:

代理模式的關鍵點是:代理物件與目標物件.代理物件是對目標物件的擴充套件,並會呼叫目標物件.

1.1.靜態代理

靜態代理在使用時,需要定義介面或者父類,被代理物件與代理物件一起實現相同的介面或者是繼承相同父類.

下面舉個案例來解釋:

模擬儲存動作,定義一個儲存動作的介面:IUserDao.java,然後目標物件實現這個介面的方法UserDao.java,此時如果使用靜態代理方式,就需要在代理物件(UserDaoProxy.java)中也實現IUserDao介面.呼叫的時候通過呼叫代理物件的方法來呼叫目標物件.

需要注意的是,代理物件與目標物件要實現相同的介面,然後通過呼叫相同的方法來呼叫目標物件的方法.

程式碼示例:

介面:IUserDao.java

/**

  • 介面

*/

publicinterfaceIUserDao{

voidsave();

}

目標物件:UserDao.java

/**

  • 介面實現

  • 目標物件

*/

publicclassUserDaoimplementsIUserDao{

publicvoidsave(){

System.out.println("----已經儲存資料!----");

}

}

代理物件:UserDaoProxy.java

/**

  • 代理物件,靜態代理

*/

publicclassUserDaoProxyimplementsIUserDao{

//接收儲存目標物件

privateIUserDao target;

publicUserDaoProxy(IUserDao target){

this.target=target;

}

publicvoidsave(){

System.out.println("開始事務...");

target.save();//執行目標物件的方法

System.out.println("提交事務...");

}

}

測試類:App.java

/**

  • 測試類

*/

publicclassApp{

publicstaticvoidmain(String[] args){

//目標物件

UserDao target =newUserDao();

//代理物件,把目標物件傳給代理物件,建立代理關係

UserDaoProxy proxy =newUserDaoProxy(target);

proxy.save();//執行的是代理的方法

}

}

靜態代理總結:

1.可以做到在不修改目標物件的功能前提下,對目標功能擴充套件.

2.缺點:

因為代理物件需要與目標物件實現一樣的介面,所以會有很多代理類,類太多.同時,一旦介面增加方法,目標物件與代理物件都要維護.

如何解決靜態代理中的缺點呢?答案是可以使用動態代理方式

1.2.動態代理

動態代理有以下特點:

1.代理物件,不需要實現介面

2.代理物件的生成,是利用JDK的API,動態的在記憶體中構建代理物件(需要我們指定建立代理物件/目標物件實現的介面的型別)

3.動態代理也叫做:JDK代理,介面代理

JDK中生成代理物件的API

代理類所在包:java.lang.reflect.Proxy

JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個引數,完整的寫法是:

staticObjectnewProxyInstance(ClassLoader loader,

Class[]interfaces,InvocationHandler h )

注意該方法是在Proxy類中是靜態方法,且接收的三個引數依次為:

ClassLoader loader,:指定當前目標物件使用類載入器,獲取載入器的方法是固定的

Class[] interfaces,:目標物件實現的介面的型別,使用泛型方式確認型別

InvocationHandler h:事件處理,執行目標物件的方法時,會觸發事件處理器的方法,會把當前執行目標物件的方法作為引數傳入

程式碼示例:

介面類IUserDao.java以及介面實現類,目標物件UserDao是一樣的,沒有做修改.在這個基礎上,增加一個代理工廠類(ProxyFactory.java),將代理類寫在這個地方,然後在測試類(需要使用到代理的程式碼)中先建立目標物件和代理物件的聯絡,然後代用代理物件的中同名方法.點選這裡有一份動態代理模式程式碼實戰。

代理工廠類:ProxyFactory.java

/**

  • 建立動態代理物件

  • 動態代理不需要實現介面,但是需要指定介面型別

*/

publicclassProxyFactory{

//維護一個目標物件

privateObjecttarget;

publicProxyFactory(Objecttarget){

this.target=target;

}

//給目標物件生成代理物件

publicObjectgetProxyInstance(){

returnProxy.newProxyInstance(

target.getClass().getClassLoader(),

target.getClass().getInterfaces(),

newInvocationHandler() {

@Override

publicObjectinvoke(Objectproxy, Method method,Object[] args) throws Throwable {

System.out.println("開始事務2");

//執行目標物件方法

ObjectreturnValue = method.invoke(target, args);

System.out.println("提交事務2");

returnreturnValue;

}

}

);

}

}

測試類:App.java

/**

  • 測試類

*/

publicclassApp{

publicstaticvoidmain(String[] args){

// 目標物件

IUserDao target =newUserDao();

// 【原始的型別 class cn.itcast.b_dynamic.UserDao】

System.out.println(target.getClass());

// 給目標物件,建立代理物件

IUserDao proxy = (IUserDao)newProxyFactory(target).getProxyInstance();

// class $Proxy0 記憶體中動態生成的代理物件

System.out.println(proxy.getClass());

// 執行方法 【代理物件】

proxy.save();

}

}

總結:

代理物件不需要實現介面,但是目標物件一定要實現介面,否則不能用動態代理

1.3.Cglib代理

上面的靜態代理和動態代理模式都是要求目標物件是實現一個介面的目標物件,但是有時候目標物件只是一個單獨的物件,並沒有實現任何的介面,這個時候就可以使用以目標物件子類的方式類實現代理,這種方法就叫做:Cglib代理.點選這裡有一份動態代理模式程式碼實戰。

Cglib代理,也叫作子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能的擴充套件.

JDK的動態代理有一個限制,就是使用動態代理的物件必須實現一個或多個介面,如果想代理沒有實現介面的類,就可以使用Cglib實現.

Cglib是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件java類與實現java介面.它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,為他們提供方法的interception(攔截)

Cglib包的底層是通過使用一個小而塊的位元組碼處理框架ASM來轉換位元組碼並生成新的類.不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉.

Cglib子類代理實現方法:

1.需要引入cglib的jar檔案,但是Spring的核心包中已經包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.

2.引入功能包後,就可以在記憶體中動態構建子類

3.代理的類不能為final,否則報錯

4.目標物件的方法如果為final/static,那麼就不會被攔截,即不會執行目標物件額外的業務方法.

程式碼示例:

目標物件類:UserDao.java

/**

  • 目標物件,沒有實現任何介面

*/

publicclassUserDao{

publicvoidsave(){

System.out.println("----已經儲存資料!----");

}

}

Cglib代理工廠:ProxyFactory.java

/**

  • Cglib子類代理工廠

  • 對UserDao在記憶體中動態構建一個子類物件

*/

publicclassProxyFactoryimplementsMethodInterceptor{

//維護目標物件

privateObjecttarget;

publicProxyFactory(Objecttarget) {

this.target = target;

}

//給目標物件建立一個代理物件

publicObjectgetProxyInstance(){

//1.工具類

Enhancer en =newEnhancer();

//2.設定父類

en.setSuperclass(target.getClass());

//3.設定回撥函式

en.setCallback(this);

//4.建立子類(代理物件)

returnen.create();

}

@Override

publicObjectintercept(Objectobj, Method method,Object[] args, MethodProxy proxy) throws Throwable {

System.out.println("開始事務...");

//執行目標物件的方法

ObjectreturnValue = method.invoke(target, args);

System.out.println("提交事務...");

returnreturnValue;

}

}

測試類:

/**

  • 測試類

*/

publicclassApp{

@Test

publicvoidtest()

{

//目標物件

UserDao target =newUserDao();

//代理物件

UserDao proxy = (UserDao)newProxyFactory(target).getProxyInstance();

//執行代理物件的方法

proxy.save();}}

▼在這裡順便給大家推薦一個架構交流群:617434785,裡面會分享一些資深架構師錄製的視訊錄影:有Spring,MyBatis,Netty原始碼分析,高併發、高效能、分散式、微服務架構的原理,JVM效能優化這些成為架構師必備的知識體系。

相關文章