動態(tài)代理學習

要理解和說明什么是動態(tài)代理需要先解釋面向對象中常見的設計模式------------代理模式

什么是代理模式(Proxy)

定義:給目標對象提供一個代理對象,并由代理對象控制對目標對象的引用

主要目的是為了在不改變對象具體方法的情況下實現(xiàn)對目標方法的增強,或解決直接訪問目標對象帶來的問題。

如下UML圖揭示了用戶,接口,委托類和代理之間的關系。

image

需要注意的是:

  1. 用戶僅通過接口調用接口功能,不在乎是誰提供了具體功能;
    2.RealSubject是接口的真正實現(xiàn)類,但不和用戶真正發(fā)生接觸
    3.Proxy同樣也實現(xiàn)了接口,所以可以和用戶直接接觸
    4.用戶調用Proxy時,Proxy內部調用了RealSubject的功能,且可以在此基礎上實現(xiàn)功能的增強

代理關系的直白理解:

image

如上圖,原本廠家生產(chǎn)電腦直接銷售給顧客,廠家和顧客之間直接聯(lián)系(類似最基本的調用接口的實現(xiàn)類)。但因為需要提供后續(xù)的售后和維修服務,增加了廠家的成本(開始的實現(xiàn)類功能不夠了)。廠家為了削減成本,減輕管理負擔(避免在現(xiàn)有實現(xiàn)類上的直接修改),廠家將部分業(yè)務交給代理去完成,而廠家也不再直接接觸顧客,僅將電腦提供給代理商,由代理商完成限售和保障售后服務(代理在現(xiàn)有類的基礎上提供更多的功能)。

靜態(tài)代理

如下是廠家的接口,提供了銷售電腦的方法。同時一個通用的接口是實現(xiàn)代理的基礎

package com.ed.demo;

public interface IProducer {

    public void sellComputer();
}

接下來是廠家的真正實現(xiàn)類,和實現(xiàn)接口的代理類

package com.ed.demo.impl;

import com.ed.demo.IProducer;

public class IProducerImpl implements IProducer {

    @Override
    public void sellComputer() {
        System.out.println("出售了一臺電腦");
    }
}

實現(xiàn)接口的代理類,在售賣電腦的前提下增加了售前和售后服務

package com.ed.demo.impl;

import com.ed.demo.IProducer;

public class Seller implements IProducer {

    IProducerImpl iProducer;

    public Seller(IProducerImpl iProducer) {
        super();
        this.iProducer = iProducer;
    }

    @Override
    public void sellComputer() {
        System.out.println("進行售前引導");   //Seller額外提供的售前服務
        iProducer.sellComputer();   //廠家原來的銷售電腦功能
        System.out.println("進行售后服務");   //Seller額外提供的售后服務
    }
}

那么運行通過代理方式的調用

package com.ed.demo;

import com.ed.demo.impl.IProducerImpl;
import com.ed.demo.impl.Seller;

public class ProxyTestDemo {

    public static void main(String[] args) {
        IProducerImpl iProducer = new IProducerImpl();

        Seller seller = new Seller(iProducer);
        seller.sellComputer();

    }
}

結果如下:

  進行售前引導
  出售了一臺電腦
  進行售后服務

現(xiàn)在可以看到,代理模式可以在不修改被代理對象的基礎上,通過擴展代理類,進行一些功能的附加與增強。值得注意的是,代理類和被代理類應該共同實現(xiàn)一個接口,或者是共同繼承某個類。
但靜態(tài)代理仍然存在一些缺陷,如:

  • 代理類和委托類實現(xiàn)了相同的接口,代理類通過委托類實現(xiàn)了相同的方法。這樣就出現(xiàn)了大量的代碼重復。如果接口增加一個方法,除了所有實現(xiàn)類需要實現(xiàn)這個方法外,所有代理類也需要實現(xiàn)此方法。增加了代碼維護的復雜度。
  • 代理對象只服務于一種類型的對象,如果要服務多類型的對象。勢必要為每一種對象都進行代理,靜態(tài)代理在程序規(guī)模稍大時就無法勝任了。

動態(tài)代理

在上述靜態(tài)代理中,一個靜態(tài)代理只能服務于一個接口,實際開發(fā)中必然導致代理類過多。

動態(tài)代理利用反射機制,在程序運行時才根據(jù)需求實現(xiàn)一個被代理對象的接口,而不需要定義具體的代理類(上述中的Seller)。

下面通過動態(tài)代理來實現(xiàn)經(jīng)銷商的功能,首先創(chuàng)建一個InvocationHandler的實現(xiàn)類

package com.ed.demo.impl;

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

public class MyInvocationHandler implements InvocationHandler {

    private Object producer;

    public MyInvocationHandler(Object producer) {
        this.producer = producer;
    }

    /**
     * 
     * @param proxy: 被代理的對象
     * @param method: 要調用的方法
     * @param args: 方法調用時所需要參數(shù)
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        System.out.println("進行售前服務");
        method.invoke(producer, args);
        System.out.println("進行售后服務");
        return null;
    }
}

InvocationHandler 內部只包含一個 invoke() 方法,正是這個方法決定了怎么樣處理代理傳遞過來的方法調用。

  • proxy 代理對象
  • method 代理對象調用的方法
  • args 調用的方法中的參數(shù)

因為,Proxy 動態(tài)產(chǎn)生的代理會調用 InvocationHandler 實現(xiàn)類,所以 InvocationHandler 是實際執(zhí)行者。

再按如下方式進行調用

import java.lang.reflect.Proxy;

public class ProxyTestDemo {

    public static void main(String[] args) {
        IProducerImpl iProducer = new IProducerImpl();

        InvocationHandler handler = new MyInvocationHandler(iProducer);

        IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
                IProducerImpl.class.getInterfaces(),
                handler);

        producer.sellComputer();


    }
}

結果如下:

  進行售前引導
  出售了一臺電腦
  進行售后服務

其中最重要的方法是

IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
                IProducerImpl.class.getInterfaces(),
                handler);

第一個參數(shù)傳入了委托類的類加載器,運行時負責將字節(jié)碼加載到JVM中并為其定義類對象。
第二個參數(shù)返回了委托類對象所引用的類實現(xiàn)的所有接口。
第三個參數(shù)是調用處理器接口,它自定義了一個 invoke 方法,用于集中處理在動態(tài)代理類對象上的方法調用。在此處中實現(xiàn)了原方法的增強。

對于動態(tài)代理而言,上述代碼中 前兩個參數(shù)幾乎是固定的。(先傳入類加載器,后傳入全部接口)

如果此時廠家同時售賣顯示器

package com.ed.demo;

public interface IProducer {

    public void sellComputer();

    public void sellMonitor();
}

委托類中進行實現(xiàn)

import com.ed.demo.IProducer;

public class IProducerImpl implements IProducer {

    @Override
    public void sellComputer() {
        System.out.println("出售了一臺電腦");
    }

    @Override
    public void sellMonitor() {
        System.out.println("出售了一臺顯示器");
    }
}

這次調用sellMonitor()

package com.ed.demo;

import com.ed.demo.impl.IProducerImpl;
import com.ed.demo.impl.MyInvocationHandler;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class ProxyTestDemo {

    public static void main(String[] args) {
        IProducerImpl iProducer = new IProducerImpl();

        InvocationHandler handler = new MyInvocationHandler(iProducer);

        IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
                IProducerImpl.class.getInterfaces(),
                handler);

        producer.sellMonitor();


    }
}

可以看到代理仍然提供了售前售后服務

  進行售前服務
  出售了一臺顯示器
  進行售后服務

而且如果有另外的Iproducer實現(xiàn)類作為新的委托類,也可以以同樣的方法享受代理服務,這里不再演示。

有選擇性的中轉

在上述代碼基礎上,對MyInvocationHandler做如下修改

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if(method.getName().equals("sellMonitor")){
            //如果是出售顯示器則不進行售后服務
            method.invoke(producer, args);
            return null;
        }
        System.out.println("進行售前服務");
        method.invoke(producer, args);
        System.out.println("進行售后服務");
        return null;
    }
}

不對銷售的顯示器進行售后服務。那么再次調用

public class ProxyTestDemo {

    public static void main(String[] args) {
        IProducerImpl iProducer = new IProducerImpl();

        InvocationHandler handler = new MyInvocationHandler(iProducer);

        IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
                IProducerImpl.class.getInterfaces(),
                handler);

        producer.sellMonitor();


    }
}

則結果中就沒有售前售后服務

 出售了一臺顯示器

可以看到利用動態(tài)代理某個接口下的多個引用可以集中到一處進行方法增強或者有選擇性的進行代理工作。

動態(tài)代理的優(yōu)點

與靜態(tài)代理相比較,最大的好處是接口中的所有方法都轉移到調用處理器的一個集中的方法(InvocationHandler.invoke)進行處理。同時接口方法數(shù)量較多時也可以靈活處理。

部分源碼解析

未完待續(xù)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容