設計模式:代理模式

前言

代理模式是指爲其他對象提供一種代理,以控制對這個對象的訪問
特點:代理對象在客戶端和目標對象之間起到中介的作用

代理模式(英語:Proxy Pattern)是程序設計中的一種設計模式。
所謂的代理者是指一個類別可以作爲其它東西的接口。代理者可以作任何東西的接口:網絡連接、存儲器中的大對象、文件或其它昂貴或無法複製的資源。

瞭解代理模式的同學都知道,根據代理類生成的方式,可以分爲靜態代理動態代理靜態代理是指開發者手動爲目標類創建一個代理類,動態代理是指根據一系列規則,由JDK或者第三方類庫動態生成代理類。

靜態代理

靜態代理相對動態代理稍微簡單一些,因爲代理類完完全全由開發者來編寫。假設一個租房場景,用靜態代理來實現
首先有一個目標類就是房東

/**
 * 房東
 * 目標類
 * @author sicimike
 * @create 2020-02-26 19:35
 */
public class Landlord {

    public void signContract() {
        System.out.println("房東籤合同...");
    }
}

房東只需要籤合同就行了(核心服務),而其餘非核心服務都由代理來完成。
代理類就是中介或者二房東

/**
 * 房東代理(中介、二房東)
 * 代理類
 * @author sicimike
 * @create 2020-02-26 19:37
 */
public class LandlordProxy {

    private Landlord landlord = new Landlord();

    public void rentHouse() {
        System.out.println("草擬合同...");
        landlord.signContract();
        System.out.println("查水錶...");
    }
}

代理類的作用就是把房子租出去,包括草擬合同,查水錶等等,核心服務籤合同還是由房東來完成。但是中介不會讓你見到房東,也就是代理類實現了對目標類的訪問控制。

測試代碼

/**
 * @author sicimike
 * @create 2020-02-26 19:40
 */
public class StaticProxyDemo {

    public static void main(String[] args) {
        LandlordProxy landlordProxy = new LandlordProxy();
        landlordProxy.rentHouse();
    }
}

執行結果

草擬合同...
房東籤合同...
查水錶...

這樣租戶在不接觸房東的情況下,通過代理實現了租房的需求。

靜態代理就是開發者需要爲每個目標類創建一個代理類。這樣的話導致工作量劇增,並且類的數量過多,不易維護,於是便產生了動態代理

動態代理

動態代理是指程序在運行的過程中,根據一系列的規則,爲目標類動態生成代理類。根據其實現方式不同,動態代理可以分成JDK動態代理Cglib動態代理

JDK動態代理

JDK是通過反射來創建代理類的,相關的類和接口主要有兩個InvocationHandler接口和Proxy類,先看實例
首先創建目標接口及其實現類,因爲JDK動態代理是針對接口的,所以目標類(被代理的類)必須實現接口

/**
 * 租賃服務
 * @author sicimike
 * @create 2020-02-26 20:08
 */
public interface RentService {

    public void signContract();
}
/**
 * @author sicimike
 * @create 2020-02-26 20:09
 */
public class RentServiceImpl implements RentService {
    @Override
    public void signContract() {
        System.out.println("簽署合同....");
    }
}

再創建實現動態代理的關鍵類,用來採集日誌

/**
 * 日誌處理器
 * @author sicimike
 * @create 2020-02-26 20:04
 */
public class LogHandlerProxy implements InvocationHandler {

    // 目標對象
    private Object target;

    public LogHandlerProxy(Object target) {
        this.target = target;
    }

    public Object bind() {
        Class aClass = target.getClass();
        return Proxy.newProxyInstance(aClass.getClassLoader(), aClass.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        befor();
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    public void befor() {
        System.out.println("日誌採集...");
    }

    public void after() {
        System.out.println("執行成功...");
    }
}

需要關注的幾點是:

  • 動態代理的實現需要實現InvocationHandler接口,接口中只有一個方法invoke。方法中三個參數分別表示:代理對象、要執行的方法、方法參數
  • 由於動態代理所代理的類是不確定的,所以targetObject類型
  • 代理對象的產生是通過Proxy#newProxyInstance方法,三個參數分別表示:加載目標類的類加載器、目標類中所有接口、實現了InvocationHandler接口的類的實例

測試代碼

/**
 * @author sicimike
 * @create 2020-02-26 20:10
 */
public class JdkDynamicProxyDemo {

    public static void main(String[] args) {
        // RentService必須是接口
        RentService proxy = (RentService) new LogHandlerProxy(new RentServiceImpl()).bind();
        proxy.signContract();
    }
}

執行結果

日誌採集...
簽署合同....
執行成功...

所以JDK動態代理是靠反射來實現的,核心類就是InvocationHandlerProxy

Cglib動態代理

Cglib是利用ASM(字節碼增強)技術來實現動態代理,也就是直接修改字節碼。根據目標類生成一個子類作爲代理類,所以目標類以及被代理的方法均不能被final修飾
使用Cglib需要引入相關的jar包

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.4</version>
</dependency>

Cglib方式實現動態代理主要涉及到一個接口net.sf.cglib.proxy.MethodInterceptor和一個類net.sf.cglib.proxy.Enhancer。先來看個實例
首先創建目標類

/**
 * 被代理的類和方法均不能被final修飾
 * @author sicimike
 * @create 2020-02-26 21:13
 */
public class RentServiceImpl {

    public void signContract() {
        System.out.println("簽署合同....");
    }
}

再創建實現動態代理的關鍵類,用來採集日誌

/**
 * 日誌處理器
 * @author sicimike
 * @create 2020-02-26 21:14
 */
public class LogHandlerProxy implements MethodInterceptor {

    // 目標對象
    private Object target;

    public LogHandlerProxy(Object target) {
        this.target = target;
    }

    public Object bind() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    /**
     * @param object 表示要進行增強的對象
     * @param method 表示攔截的方法
     * @param args 數組表示參數列表,基本數據類型需要傳入其包裝類型
     * @param methodProxy 用於調用父類方法
     */
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    public void before() {
        System.out.println("日誌採集...");
    }

    public void after() {
        System.out.println("執行成功...");
    }
}

需要關注的幾點是:

  • 動態代理的實現需要實現MethodInterceptor接口,接口中只有一個方法intercept。方法中四個參數已經分別在代碼中註明
  • 由於動態代理所代理的類是不確定的,所以targetObject類型
  • 代理對象的產生是通過Enhancer#create方法

測試代碼

/**
 * @author sicimike
 * @create 2020-02-26 21:19
 */
public class CglibDynamicProxyDemo {

    public static void main(String[] args) {
        RentServiceImpl proxy = (RentServiceImpl) new LogHandlerProxy(new RentServiceImpl()).bind();
        proxy.signContract();
    }
}

執行結果

日誌採集...
簽署合同....
執行成功...

所以Cglib動態代理是靠字節碼增強技術來實現的,核心類就是MethodInterceptorEnhancer

JDK動態代理和Cglib動態代理

  • Bean有實現接口時,Spring就會使用JDK實現動態代理
  • Bean沒有實現接口時,Spring使用Cglib實現動態代理
  • 可以強制使用Cglib,在Spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

代理模式優點

  • 代理模式能將代理對象與真實被調用的目標對象分離
  • 一定程度上降低了系統的耦合度,擴展性好
  • 保護目標對象
  • 增強目標對象

代理模式缺點

  • 代理模式會造成系統設計中類數目的增加
  • 在客戶端和目標對象增加一個代理對象,會造成請求處理速度變慢
  • 增加系統複雜度

源代碼

Proxy

總結

代理模式是一種非常重要的設計模式,Spring核心組件AOP,就是通過動態代理來實現的。所以學好了動態代理,就學好了一半的Spring。
張學友

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章