通俗易懂的讲解一下Java的代理模式
一、基本概念
代理模式是對(duì)象的結(jié)構(gòu)模式。
代理模式給某一個(gè)對(duì)象提供一個(gè)代理對(duì)象,并由代理對(duì)象控制對(duì)原對(duì)象的引用(接口的引用)
二、靜態(tài)代理
靜態(tài)代理是指,代理類在程序運(yùn)行前就已經(jīng)定義好,其與**目標(biāo)類(被代理類)**的關(guān)系在程序運(yùn)行前就已經(jīng)確立。
靜態(tài)代理類似于企業(yè)與企業(yè)的法律顧問間的關(guān)系。法律顧問與企業(yè)的代理關(guān)系,并不是在“官司“發(fā)生后才建立的,而是之前就確立好的一種關(guān)系。
而動(dòng)態(tài)代理就是外面打官司一樣,是官司發(fā)生了之后臨時(shí)請(qǐng)的律師。
代理可以看做就是在被代理對(duì)象外面包裹一層(和裝飾者類似但又不同):
案例: 比如我們有一個(gè)可以移動(dòng)的坦克,它的主要方法是move(),但是我們需要記錄它移動(dòng)的時(shí)間,以及在它移動(dòng)前后做日志,其靜態(tài)代理的實(shí)現(xiàn)模式就類似下面的圖:
兩個(gè)代理類以及結(jié)構(gòu)關(guān)系:
代碼:
public interface Movable {void move(); } public class Tank implements Movable {@Overridepublic void move() {// 坦克移動(dòng)System.out.println("Tank Moving......");try {Thread.sleep(new Random().nextInt(5000)); // 隨機(jī)產(chǎn)生 1~5秒, 模擬坦克在移動(dòng) } catch (InterruptedException e) {e.printStackTrace();}} }復(fù)制代碼兩個(gè)代理類: TankTimeProxy和TankLogProxy:
public class TankTimeProxy implements Movable {private Movable tank;public TankTimeProxy(Movable tank) {this.tank = tank;}@Overridepublic void move() {// 在前面做一些事情: 記錄開始時(shí)間long start = System.currentTimeMillis();System.out.println("start time : " + start);tank.move();// 在后面做一些事情: 記錄結(jié)束時(shí)間,并計(jì)算move()運(yùn)行時(shí)間long end = System.currentTimeMillis();System.out.println("end time : " + end);System.out.println("spend all time : " + (end - start)/1000 + "s.");} } public class TankLogProxy implements Movable {private Movable tank;public TankLogProxy(Movable tank) {this.tank = tank;}@Overridepublic void move() {// tank 移動(dòng)前記錄日志System.out.println("Tank Log start.......");tank.move();// tank 移動(dòng)后記錄日志System.out.println("Tank Log end.......");} }復(fù)制代碼測(cè)試:
public class Client {public static void main(String[] args){Movable target = new TankLogProxy(new TankTimeProxy(new Tank())); //先記錄時(shí)間,再記錄日志 // Movable target = new TankTimeProxy(new TankLogProxy(new Tank())); //先記錄日志,再記錄時(shí)間target.move();} }復(fù)制代碼輸出:
Tank Log start....... start time : 1551271511619 Tank Moving...... end time : 1551271514522 spend all time : 2s. Tank Log end.......復(fù)制代碼這其中有兩個(gè)很重要的點(diǎn),那就是:
- 兩個(gè)代理對(duì)象內(nèi)部都有著被代理對(duì)象(target)實(shí)現(xiàn)的接口的引用;
- 且兩個(gè)代理對(duì)象都實(shí)現(xiàn)了被代理對(duì)象(target)實(shí)現(xiàn)的接口;
三、基本動(dòng)態(tài)代理
上面靜態(tài)代理的缺點(diǎn)在哪?
現(xiàn)在單看做時(shí)間這個(gè)代理,如果我們現(xiàn)在多了一個(gè)飛機(jī),飛機(jī)里面的方法是fly(),現(xiàn)在要給飛機(jī)做代理,那么我們不能用之前寫的TankTimeProxy,我們需要額外的寫一個(gè)PlaneTimeProxy,這明顯是冗余代碼,所以這就是靜態(tài)代理最大的缺點(diǎn),這可以用動(dòng)態(tài)代理解決。
動(dòng)態(tài)代理是指,程序在整個(gè)運(yùn)行過程中根本就不存在目標(biāo)類的代理類(在JDK內(nèi)部叫$Proxy0,我們看不到),目標(biāo)對(duì)象的代理對(duì)象只是由代理生成工具(如代理工廠類) 在程序運(yùn)行時(shí)由 JVM 根據(jù)反射等機(jī)制動(dòng)態(tài)生成的。代理對(duì)象與目標(biāo)對(duì)象的代理關(guān)系在程序運(yùn)行時(shí)才確立。
對(duì)比靜態(tài)代理,靜態(tài)代理是指在程序運(yùn)行前就已經(jīng)定義好了目標(biāo)類的代理類。代理類與目標(biāo)類的代理關(guān)系在程序運(yùn)行之前就確立了。
首先看動(dòng)態(tài)代理的一些特點(diǎn):
- 動(dòng)態(tài)代理不需要寫出代理類的名字,你要的代理對(duì)象我直接給你產(chǎn)生,是使用的時(shí)候生成的;
- 只需要調(diào)用Proxy.newProxyInstance()就可以給你產(chǎn)生代理類;
JDK動(dòng)態(tài)代理相關(guān)API:
下面看使用動(dòng)態(tài)代理解決上面的問題(可以用TimeProxy代理一切對(duì)象):
public interface Movable {void move(); } public class Tank implements Movable {@Overridepublic void move() {// 坦克移動(dòng)System.out.println("Tank Moving......");try {Thread.sleep(new Random().nextInt(5000)); // 隨機(jī)產(chǎn)生 1~5秒, 模擬坦克在移動(dòng) } catch (InterruptedException e) {e.printStackTrace();}} }復(fù)制代碼新增的飛機(jī):
public interface Flyable {void fly(); } public class Plane implements Flyable{@Overridepublic void fly() {System.out.println("Plane Flying......");try {Thread.sleep(new Random().nextInt(5000)); // 隨機(jī)產(chǎn)生 1~5秒, 飛機(jī)在飛行 } catch (InterruptedException e) {e.printStackTrace();}} }復(fù)制代碼我們的關(guān)鍵處理,即編寫MyTimeProxyInvocationHandler:
// 靜態(tài)代理做不到既為飛機(jī)做時(shí)間代理,又為坦克做時(shí)間代理,但是動(dòng)態(tài)代理可以為所有對(duì)象做代理 public class MyTimeProxyInvocationHandler implements InvocationHandler {private Object target;//注意這里是 Object ,不是Movable或者Flyablepublic MyTimeProxyInvocationHandler(Object target) {this.target = target;}// proxy : 代理對(duì)象 可以是一切對(duì)象 (Object)// method : 目標(biāo)方法// args : 目標(biāo)方法的參數(shù)@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 在前面做一些事情: 記錄開始時(shí)間long start = System.currentTimeMillis();System.out.println("start time : " + start);method.invoke(target, args); // 調(diào)用目標(biāo)方法 invoke是調(diào)用的意思, 可以有返回值的方法(我們這里move和fly都沒有返回值)// 在后面做一些事情: 記錄結(jié)束時(shí)間,并計(jì)算move()運(yùn)行時(shí)間long end = System.currentTimeMillis();System.out.println("end time : " + end);System.out.println("spend all time : " + (end - start)/1000 + "s.");return null;} }復(fù)制代碼最后測(cè)試類:
public class Client {public static void main(String[] args){Movable tank = new Tank();//可以為所有對(duì)象產(chǎn)生時(shí)間代理的 InvocationHandlerMyTimeProxyInvocationHandler myInvocationHandler = new MyTimeProxyInvocationHandler(tank);Movable tankProxy = (Movable) Proxy.newProxyInstance(tank.getClass().getClassLoader(),tank.getClass().getInterfaces(),myInvocationHandler);tankProxy.move();System.out.println("--------------------");Flyable plane = new Plane();myInvocationHandler = new MyTimeProxyInvocationHandler(plane);// 為飛機(jī)產(chǎn)生代理, 為..產(chǎn)生代理,這樣可以為很多東西產(chǎn)生代理,靜態(tài)代理做不到Flyable planeProxy = (Flyable) Proxy.newProxyInstance(plane.getClass().getClassLoader(),plane.getClass().getInterfaces(),myInvocationHandler);planeProxy.fly();} }復(fù)制代碼輸出(同時(shí)為Tank和Plane做了代理):
start time : 1551275526486 Tank Moving...... end time : 1551275531193 spend all time : 4s. -------------------- start time : 1551275531195 Plane Flying...... end time : 1551275532996 spend all time : 1s.復(fù)制代碼我們分析一下這個(gè)代理過程:
調(diào)用過程(重要):
- JDK內(nèi)部的Proxy類在內(nèi)部創(chuàng)建了一個(gè)$Proxy0的代理對(duì)象(它實(shí)現(xiàn)了目標(biāo)對(duì)象所在接口Movable;
- $Proxy0內(nèi)部有InvocationHandler接口的引用,然后在$Proxy中調(diào)用了接口的invoke()方法;
- 而我們將InvocationHandler接口的實(shí)現(xiàn)類傳入了Proxy,所以我們?cè)趯?shí)現(xiàn)類中加入的前后邏輯就會(huì)得到執(zhí)行;
如果這里還不夠理解,可以看代理模式(二),會(huì)模擬實(shí)現(xiàn)JDK的底層實(shí)現(xiàn)。
四、CGLIB動(dòng)態(tài)代理
問題: 使用 JDK 的 Proxy 實(shí)現(xiàn)代理,要求目標(biāo)類與代理類實(shí)現(xiàn)相同的接口。若目標(biāo)類不存在接口,則無法使用該方式實(shí)現(xiàn)。
可以用 CGLIB 來解決上面的問題。
CGLIB 代理的生成原理是生成目標(biāo)類的子類,而子類是增強(qiáng)過的,這個(gè)子類對(duì)象就是代理對(duì)象。
所以,使用CGLIB 生成動(dòng)態(tài)代理,要求目標(biāo)類必須能夠被繼承,即不能是 final 的類。
基本結(jié)構(gòu):
代碼:
Tank類(沒有接口)
// 沒有實(shí)現(xiàn)接口 public class Tank {public void move() {// 坦克移動(dòng)System.out.println("Tank Moving......");try {Thread.sleep(new Random().nextInt(5000)); // 隨機(jī)產(chǎn)生 1~5秒, 模擬坦克在移動(dòng) } catch (InterruptedException e) {e.printStackTrace();}} }復(fù)制代碼MyCglibFactory類:
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method;//需要實(shí)現(xiàn)MethodInterceptor, 當(dāng)前這個(gè)類的對(duì)象就是一個(gè)回調(diào)對(duì)象 // MyCglibFactory 是 類A,它調(diào)用了Enhancer(類B)的方法: setCallback(this),而且將類A對(duì)象傳給了類B // 而類A 的 方法intercept會(huì)被類B的 setCallback調(diào)用,這就是回調(diào)設(shè)計(jì)模式 public class MyCglibFactory implements MethodInterceptor { //public interface MethodInterceptor extends Callbackprivate Tank target;public MyCglibFactory(Tank target) {this.target = target;}public Tank myCglibCreator() {Enhancer enhancer = new Enhancer();// 設(shè)置需要代理的對(duì)象 : 目標(biāo)類(target) , 也是父類enhancer.setSuperclass(Tank.class);// 設(shè)置代理對(duì)象, 這是回調(diào)設(shè)計(jì)模式: 設(shè)置回調(diào)接口對(duì)象 :enhancer.setCallback(this); // this代表當(dāng)前類的對(duì)象,因?yàn)楫?dāng)前類實(shí)現(xiàn)了Callbackreturn (Tank) enhancer.create();}// 這個(gè)就是回調(diào)方法(類A的方法)@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {// 在前面做一些事情: 記錄開始時(shí)間long start = System.currentTimeMillis();System.out.println("start time : " + start);method.invoke(target, args);// 在后面做一些事情: 記錄結(jié)束時(shí)間,并計(jì)算move()運(yùn)行時(shí)間long end = System.currentTimeMillis();System.out.println("end time : " + end);System.out.println("spend all time : " + (end - start)/1000 + "s.");return null;} }復(fù)制代碼測(cè)試:
public class Client {public static void main(String[] args){Tank proxyTank = new MyCglibFactory(new Tank()).myCglibCreator();proxyTank.move();} }復(fù)制代碼輸出(進(jìn)行了時(shí)間代理TimeProxy):
start time : 1551327522964 Tank Moving...... end time : 1551327526214 spend all time : 3s.復(fù)制代碼上面的設(shè)計(jì)模式用到了回調(diào)設(shè)計(jì)模式: 在 Java 中,類 A 調(diào)用類 B 中的某個(gè)方法 b(),然后類 B 又在某個(gè)時(shí)候反過來調(diào)用類 A中的某個(gè)方法 a(),對(duì)于 A來說,這個(gè) a() 方法便叫做回調(diào)方法。
Java 的接口提供了一種很好的方式來實(shí)現(xiàn)方法回調(diào)。這個(gè)方式就是定義一個(gè)簡(jiǎn)單的接口,在接口之中定義一個(gè)我們希望回調(diào)的方法。這個(gè)接口稱為回調(diào)接口。(Callback) 在前面的例子中,我們定義的 MyCglibFactory 類就相當(dāng)于前面所說的 A類,而 Enhancer 類則是 B 類。A 類中調(diào)用了 Enhancer 類的 setCallback(this)方法,并將回調(diào)對(duì)象 this 作為實(shí)參傳遞給了Enhancer 類。Enhancer 類在后續(xù)執(zhí)行過程中,會(huì)調(diào)用A類中的intercept()方法,這個(gè) intercept()方法就是回調(diào)方法。
轉(zhuǎn)載于:https://juejin.im/post/5cffa9266fb9a07efe2db3c7
總結(jié)
以上是生活随笔為你收集整理的通俗易懂的讲解一下Java的代理模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 批处理(cmd)的学习记录
- 下一篇: Java Web学习总结(6)——通过S