java代理:靜態代理和動態代理


這里總結下java中的靜態代理和動態代理。

Java中有一個設計模式是代理模式

代理模式是常用的Java設計模式,特征是代理類與委托類有相同的接口,代理類主要負責為委托類預處理消息、過濾消息、把消息轉發給委托類,以及事后處理消息等。

代理類與委托類之間通常會存在關聯關系,一個代理類的對象與一個委托類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委托類的對象的相關方法,來提供特定的服務。簡單的說就是,我們在訪問實際對象的時候,是通過代理對象來訪問的,代理模式就是在訪問實際對象的時候引入一定程度的間接性,因為這種間接性,可以附加多種用途。

靜態代理

靜態代理,由程序員創建或特定工具自動生成源代碼,在編譯時已經將接口,被代理類(委托類),代理類等確定下來。在程序運行之前,代理類的.class文件就已經生成。

假定一個團購長途汽車車票的場景,有50個乘客要去客運站買長途汽車車票,由跟車人去代買50張車票。在這里,乘客有買車票的行為,跟車人也有買車票的行為,那么乘客買車票就可以由跟車人去代理執行。

首先是創建一個買票人的接口。

/**
 * 買票人接口
 */
public interface TickectBuyer {

    // 買車票
    void buyTicket();
}

然后是創建一個乘客類(委托類),去實現買票人接口。

/**
 * 乘客類,實現了買票人接口
 */
public class Passenger implements TickectBuyer {

    private String name;

    Passenger(String name) {
        this.name = name;
    }

    @Override
    public void buyTicket() {
        System.out.println("乘客【" + name + "】買了一張車票。");
    }
}

然后是創建一個乘客代理類,同樣實現買票人接口。

因為持有一個乘客類對象,所以它可以代理乘客類對象執行買車票的行為。

/**
 * 乘客代理類,也實現買票人接口
 */
public class PassengerProxy implements TickectBuyer {

    private String name;

    // 被代理的乘客類
    private Passenger passenger;

    PassengerProxy(String name, Passenger passenger) {

        this.name = name;

        // 只代理乘客類
        if (passenger.getClass() == Passenger.class) {
            this.passenger = passenger;
        }
    }

    @Override
    public void buyTicket() {
        // 委托類附加的操作
        System.out.print("代買人【" + name + "】代");

        // 調用委托類(乘客類)的方法
        passenger.buyTicket();
    }
}

最后創建一個測試類測試代理的結果。

/**
 * 乘客代理測試類
 */
public class PassengerProxyTest {

    public static void main(String[] args) {
        // 乘客陳小雞(乘客類)
        Passenger passenger = new Passenger("陳小雞");
        // 跟車人(乘客代理類)
        PassengerProxy carFollower = new PassengerProxy("王小狗", passenger);
        // 由跟車人代理陳小雞買車票
        carFollower.buyTicket();
    }
}

結果是:代買人【王小狗】代乘客【陳小雞】買了一張車票。

這里可以看到,代理類可以通過持有委托類對象去調用委托類的方法,從而達到代理委托類去執行委托類行為的目的。然后,在調用委托類方法的時候,可以在調用的前面或者后面添加代理類自己的行為,比如上面代碼中添加打印代理人信息的行為。這個就是代理模式的一個很大的優點,可以在代理點切入一些特定的、附加的操作,卻不會改變原來委托要執行的行為。

動態代理

代理類在程序運行時常見的代理方式被稱為動態代理。我們上面靜態代理的例子中,代理類PassengerProxy是自己定義好的,在程序運行之前就已經編譯完成。不同的是,動態代理的代理類並不是在Java代碼中定義好的,而是在運行時根據我們在Java代碼中的指示動態生成的。相比於靜態代理,動態代理的優勢在於可以很方便地對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。比如說,想要在每個代理的方法前都加上一個處理方法:

public void buyTicket() {
    // 在調用委托類的方法前,加入其他邏輯處理
    beforeMethod();

    // 調用委托類(乘客類)的方法
    passenger.buyTicket();
}

這里只有一個buyTickect()方法,就只要寫一次beforeMethod()方法。可是如果有很多個地方都要調用beforeMethod()方法,就需要改很多個地方,給修改或維護帶來麻煩。動態代理就是為了解決這樣的麻煩,而由聰明絕頂(滑稽)的人才想出來的解決方法。

在Java的java.lang.reflect包(反射包啦,看到這應該明白動態代理是用Java的反射機制實現的了吧,不會反射的還不去先學一下反射)下提供了一個Proxy類和InvocationHandler接口,通過這個類和這個接口可以生成JDK動態代理類和動態代理對象。

創建一個InvocationHandler對象。

// 創建一個與代理對象相關聯的InvocationHandler
InvocationHandler passengerHandler = new MyInvocationHandler<TickertBuyer>(passenger);

使用Proxy類的getProxyClass靜態方法生成一個動態代理類passengerProxyClass。

Class<?> passengerProxyClass = Proxy.getProxyClass(TickectBuyer.class.getClassLoader(), new Class<?>[] {TickectBuyer.class});

獲得passengerProxy中一個帶InvocationHandler參數的構造器constructor。

Constructor<?> constructor = passengerProxy.getConstructor(InvocationHandler.class);

通過構造器constructor來創建一個動態實例passengerProxy。

TickectBuyer passengerProxy = (TickectBuyer) constructor.newInstance(passengerHandler);

這樣,一個動態代理對象passengerProxy就創建完畢了。另外的,上面四個步驟可以通過Proxy類的newProxyInstancs方法來簡化:

// 創建一個與代理對象相關聯的InvocationHandler
InvocationHandler passengerHandler = new MyInvocationHandler<TickectBuyer>(passenger);
// 創建一個代理對象passengerProxy,代理對象的每個執行方法都會替換執行Invocation中的invoke方法
TickectBuyer passengerProxy= (TickectBuyer) Proxy.newProxyInstance(TickectBuyer.class.getClassLoader(), new Class<?>[]{TickectBuyer.class}, passengerHandler);

那么動態代理是要如何執行,如何通過代理對象來執行被代理對象的方法呢。我們可以通過完整的動態代理的例子來說明。還是上面跟車人幫乘客代買車票的例子。

首先是定義一個TickectBuyer接口,其中有一個未實現的buyTickect()方法。

public interface TickectBuyer {

    // 買車票
    void buyTicket();
}

然后是創建需要被代理的乘客類。

public class Passenger implements TickectBuyer {

    private String name;

    Passenger(String name) {
        this.name = name;
    }

    @Override
    public void buyTicket() {
        try {
            // 假設買一張票要5秒
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("乘客【" + name + "】買了一張車票。");
    }
}

然后定義一個檢測方法執行時間的工具類,在任何方法執行之前先調用start()方法,執行后調用finish()方法,就可以計算出該方法的運行時間,這也是一個最簡單的方法執行時間檢測工具。

public class MonitorUtil {

    private static ThreadLocal<Long> tl = new ThreadLocal<>();

    public static void start() {
        tl.set(System.currentTimeMillis());
    }

    // 結束時打印耗時
    public static void finish(String methodName) {
        long finishTime = System.currentTimeMillis();
        System.out.println(methodName + "方法耗時" + (finishTime - tl.get()) + "ms");
    }
}

然后創建PassengerInvocationHandler類,實現InvocationHandler接口。這個類中持有一個被代理對象的實例target。InvocationHandler中有一個invoke()方法,所有執行代理對象的方法都會被替換成執行invoke()方法。在invoke()方法中執行被代理對象target的相應方法。當然,在代理過程中,我們可以在真正執行被代理對象的方法前加入自己的其他處理。這也是Spring中AOP實現的主要原理,其實就是Java基礎的反射機制,沒有什么神秘的黑科技啦。

public class PassengerInvocationHandler<T> implements InvocationHandler {
    // InvocationHandler持有的被代理對象
    private T target;

    public PassengerInvocationHandler(T target) {
        this.target = target;
    }

    /**
     * proxy:代表動態代理對象
     * method:代表正在執行的方法
     * args:代表調用目標方法時傳入的實參
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理執行" + method.getName() + "方法");

        // 代理過程中插入監測方法,計算該方法耗時
        MonitorUtil.start();
        Object result = method.invoke(target, args);
        MonitorUtil.finish(method.getName());
        return result;
    }
}

做完上面的工作后,我們就可以具體來創建動態代理對象了。

public class PassengerProxyTest2 {

    public static void main(String[] args) {
        
        // 創建一個實例對象,這個對象是被代理的對象
        TickectBuyer zhangsan = new Passenger("張三");

        // 創建一個與代理對象相關聯的InvocationHandler
        InvocationHandler passengerHandler = new PassengerInvocationHandler<TickectBuyer>(zhangsan);

        // 創建一個代理對象passengerProxy來代理zhangsan,代理對象的每個執行方法都會替換執行Invocation中的invoke方法
        TickectBuyer passengerProxy = (TickectBuyer) Proxy.newProxyInstance(TickectBuyer.class.getClassLoader(),
                new Class<?>[]{TickectBuyer.class}, passengerHandler);

        // 代理執行買車票的方法
        passengerProxy.buyTicket();
    }
}

我們執行這個PassengerProxyTest2類之前,先想以下,我們創建了一個需要被代理的乘客張三,將張三對象傳給了passengerHandler,在創建代理對象passengerProxy時,將passengerHandler作為參數,上面有說到所有執行代理對象的方法都會被替換成執行invoke()方法,也就是說,最后執行的是passengerInvocationHandler中的invoke()方法。

上面說到,動態代理的優勢在於可以很方便地對代理類的方法進行統一的處理,而不用修改每個代理類中的方法,這是因為所有被代理執行的方法,都是通過InvocationHandler中的invoke()方法調用的,我們只要在invoke()方法中統一處理,就可以給所有被代理的方法進行統一添加相同的操作了。例如這里的方法計時,所有的被代理對象執行的方法都會被計時。

動態代理的過程,代理對象和被代理對象的關系不像靜態代理那樣一目了然,清晰明了。因為動態代理的過程中,我們並沒有實際看到代理類,也沒有很清晰地看到動態代理中被代理對象是怎么被代理的,也不知道為什么代理對象執行的方法都會通過InvocationHandler中的invoke()方法執行。為了弄清楚這些問題,就需要分析源碼碼,下次再用另外的篇幅來分析好了。

 

"暗戀或單戀都是人生中一場漫長的走神。"


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM