Java代理模式及動態代理詳解

Java的動態代理在實踐中有着普遍的使用場景,好比最場景的Spring AOP、Java註解的獲取、日誌、用戶鑑權等。本篇文章帶你們瞭解一下代理模式、靜態代理以及基於JDK原生動態代理。java

代理模式

不管學習靜態代理或動態代理,咱們都要先了解一下代理模式。編程

先看百度百科的定義:微信

代理模式的定義:爲其餘對象提供一種代理以控制對這個對象的訪問。在某些狀況下,一個對象不適合或者不能直接引用另外一個對象,而代理對象能夠在客戶端和目標對象之間起到中介的做用。

直接看定義可能有些難以理解,咱們就以生活中具體的實例來講明一下。框架

咱們都去過超市購買過物品,超市從廠商那裏購買貨物以後出售給咱們,咱們一般並不知道貨物從哪裏通過多少流程纔到超市。ide

在這個過程當中,等因而廠商「委託」超市出售貨物,對咱們來講是廠商(真實對象)是不可見的。而超市(代理對象)呢,做爲廠商的「代理者」來與咱們進行交互。函數

同時,超市還能夠根據具體的銷售狀況來進行折扣等處理,來豐富被代理對象的功能。學習

經過代理模式,咱們能夠作到兩點:測試

一、隱藏委託類的具體實現。this

二、實現客戶與委託類的解耦,在不改變委託類代碼的狀況下添加一些額外的功能(日誌、權限)等。spa

代理模式角色定義

在上述的過程當中在編程的過程當中咱們能夠定義爲三類對象:

  • Subject(抽象主題角色):定義代理類和真實主題的公共對外方法,也是代理類代理真實主題的方法。好比:廣告、出售等。
  • RealSubject(真實主題角色):真正實現業務邏輯的類。好比實現了廣告、出售等方法的廠家(Vendor)。
  • Proxy(代理主題角色):用來代理和封裝真實主題。好比,一樣實現了廣告、出售等方法的超時(Shop)。

以上三個角色對應的類圖以下:

Java代理及動態代理詳解

靜態代理實例

靜態代理是指代理類在程序運行前就已經存在,這種狀況下的代理類一般都是咱們在Java代碼中定義的。

下面咱們就以具體的實例來演示一下靜態代理。

首先定義一組接口Sell,用來提供廣告和銷售等功能。而後提供Vendor類(廠商,被代理對象)和Shop(超市,代理類),它們分別實現了Sell接口。

Sell接口定義以下:

/**
 * 委託類和代理類都實現了Sell接口
 * @author sec
 * @version 1.0
 * @date 2020/3/21 9:30 AM
 **/
public interface Sell {

    /**
     * 出售
     */
    void sell();

    /**
     * 廣告
     */
    void ad();
}

Vendor類定義以下:

/**
 * 供應商
 * @author sec
 * @version 1.0
 * @date 2020/3/21 9:30 AM
 **/
public class Vendor implements Sell{

    @Override
    public void sell() {
        System.out.println("Shop sell goods");
    }

    @Override
    public void ad() {
        System.out.println("Shop advert goods");
    }
}

Shop類定義以下:

/**
 * 超市,代理類
 * @author sec
 * @version 1.0
 * @date 2020/3/21 9:30 AM
 **/
public class Shop implements Sell{

    private Sell sell;

    public Shop(Sell sell){
        this.sell = sell;
    }

    @Override
    public void sell() {
        System.out.println("代理類Shop,處理sell");
        sell.sell();
    }

    @Override
    public void ad() {
        System.out.println("代理類Shop,處理ad");
        sell.ad();
    }
}

其中代理類Shop經過聚合的方式持有了被代理類Vendor的引用,並在對應的方法中調用Vendor對應的方法。在Shop類中咱們能夠新增一些額外的處理,好比篩選購買用戶、記錄日誌等操做。

下面看看在客戶端中如何使用代理類。

/**
 * 靜態代理類測試方法
 * @author sec
 * @version 1.0
 * @date 2020/3/21 9:33 AM
 **/
public class StaticProxy {

    public static void main(String[] args) {

        // 供應商---被代理類
        Vendor vendor = new Vendor();

        // 建立供應商的代理類Shop
        Sell sell = new Shop(vendor);

        // 客戶端使用時面向的是代理類Shop。
        sell.ad();
        sell.sell();
    }
}

在上述代碼中,針對客戶看到的是Sell接口提供了功能,而功能又是由Shop提供的。咱們能夠在Shop中修改或新增一些內容,而不影響被代理類Vendor。

靜態代理的缺點

靜態代理實現簡單且不侵入原代碼,但當場景複雜時,靜態代理會有如下缺點:

一、當須要代理多個類時,代理對象要實現與目標對象一致的接口。要麼,只維護一個代理類來實現多個接口,但這樣會致使代理類過於龐大。要麼,新建多個代理類,但這樣會產生過多的代理類。

二、當接口須要增長、刪除、修改方法時,目標對象與代理類都要同時修改,不易維護。

因而,動態代理便派上用場了。

動態代理

動態代理是指代理類在程序運行時進行建立的代理方式。這種狀況下,代理類並非在Java代碼中定義的,而是在運行時根據Java代碼中的「指示」動態生成的。

相比於靜態代理,動態代理的優點在於能夠很方便的對代理類的函數進行統一的處理,而不用修改每一個代理類的函數。

基於JDK原生動態代理實現

實現動態代理一般有兩種方式:JDK原生動態代理和CGLIB動態代理。這裏,咱們以JDK原生動態代理爲例來進行講解。

JDK動態代理主要涉及兩個類:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。

InvocationHandler接口定義了以下方法:

/**
 * 調用處理程序
 */
public interface InvocationHandler { 
    Object invoke(Object proxy, Method method, Object[] args); 
}

顧名思義,實現了該接口的中介類用作「調用處理器」。當調用代理類對象的方法時,這個「調用」會轉送到invoke方法中,代理類對象做爲proxy參數傳入,參數method標識了具體調用的是代理類的哪一個方法,args爲該方法的參數。這樣對代理類中的全部方法的調用都會變爲對invoke的調用,能夠在invoke方法中添加統一的處理邏輯(也能夠根據method參數對不一樣的代理類方法作不一樣的處理)。

Proxy類用於獲取指定代理對象所關聯的調用處理器。

下面以添加日誌爲例來演示一下動態代理。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

public class LogHandler implements InvocationHandler {
    Object target;  // 被代理的對象,實際的方法執行者

    public LogHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);  // 調用 target 的 method 方法
        after();
        return result;  // 返回方法的執行結果
    }
    // 調用invoke方法以前執行
    private void before() {
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    // 調用invoke方法以後執行
    private void after() {
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

客戶端編寫程序使用動態代理代碼以下:

import java.lang.reflect.Proxy;

/**
 * 動態代理測試
 *
 * @author sec
 * @version 1.0
 * @date 2020/3/21 10:40 AM
 **/
public class DynamicProxyMain {

    public static void main(String[] args) {
        // 建立中介類實例
        LogHandler logHandler = new LogHandler(new Vendor());
        // 設置該變量能夠保存動態代理類,默認名稱$Proxy0.class
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

        // 獲取代理類實例Sell
        Sell sell = (Sell) (Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[]{Sell.class}, logHandler));

        // 經過代理類對象調用代理類方法,實際上會轉到invoke方法調用
        sell.sell();
        sell.ad();
    }
}

執行以後,打印日誌以下:

調用方法sell之【前】的日誌處理
Shop sell goods
調用方法sell之【後】的日誌處理
調用方法ad之【前】的日誌處理
Shop advert goods
調用方法ad之【後】的日誌處理

通過上述驗證,咱們發現已經成功爲咱們的被代理類統一添加了執行方法以前和執行方法以後的日誌。

在上述實例中爲了看一下生成的動態代理類的代碼,咱們添加了下面的屬性設置(在生產環境中須要去掉該屬性)。

// 設置該變量能夠保存動態代理類,默認名稱$Proxy0.class
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

那麼,咱們能夠執行main方法以後,還生成了一個名字爲$Proxy0.class類文件。經過反編譯可看到以下的代碼:

package com.sun.proxy;

import com.choupangxia.proxy.Sell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Sell {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void ad() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void sell() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.choupangxia.proxy.Sell").getMethod("ad");
            m3 = Class.forName("com.choupangxia.proxy.Sell").getMethod("sell");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

能夠看到$Proxy0(代理類)繼承了Proxy類,而且實現了被代理的全部接口,以及equals、hashCode、toString等方法。

因爲動態代理類繼承了Proxy類,因此每一個代理類都會關聯一個InvocationHandler方法調用處理器。

類和全部方法都被public final修飾,因此代理類只可被使用,不能夠再被繼承。

每一個方法都有一個Method對象來描述,Method對象在static靜態代碼塊中建立,以「m+數字」的格式命名。

調用方法的時候經過super.h.invoke(this,m1,(Object[])null);調用。其中的super.h.invoke其實是在建立代理的時候傳遞給Proxy.newProxyInstance的LogHandler對象,它繼承InvocationHandler類,負責實際的調用處理邏輯。

小結

關於代理和動態代理相關的內容,咱們就講這麼多。瞭解了代理模式可讓咱們的系統設計的更加具備可擴展性。而動態代理的應用就更廣了,各種框架及業務場景都在使用。有了兩個基礎,就可以更好的學習其餘框架。

關於CGLIB動態代理的內容,咱們下篇文章再來聊一聊。


<center>程序新視界:精彩和成長都不容錯過</center>

程序新視界-微信公衆號

相關文章
相關標籤/搜索