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