1. 程式人生 > 實用技巧 >設計模式系列:代理模式

設計模式系列:代理模式

一、問題引入

現在有這樣一個場景:假設我們想邀請一位明星,那麼一般我們不是直接聯絡明星,而是聯絡明星的經紀人,通過經紀人建立和明星的合作。在這裡,明星就是一個目標物件,他只要負責活動演出就行了,而其他瑣碎的事情就交給他的代理人(經紀人)來解決。這就是代理模式在現實中的一個應用場景。

同樣地,假如我們想要租房,我們不可能每個小區每個住戶去敲門問業主是否要出租,這樣是不可行的,太浪費時間和精力了。我們只是想要租房,為什麼要額外做這麼多事呢?很簡單,我直接通過中介公司來租房,由中介幫我們找房源,幫我們辦理租房流程,籤合同。我們只負責挑選自己喜歡的房源,然後付錢入住就可以了。這也是代理模式在現實中的一個應用。

二、模式定義

代理(Proxy)是一種設計模式,又叫委託模式,提供了對目標物件另外的訪問方式,即通過代理物件去訪問目標物件,由代理物件控制對原物件的訪問。代理模式通俗來講就是我們生活中常見的中介。這樣做的好處是:可以在目標物件實現的基礎上,增加額外的功能,即擴充套件目標物件的功能。同時還能起到隔離作用:在某些情況下,一個客戶類不想或者不能直接引用一個委託物件,而代理類物件可以在客戶類和委託物件之間起到中介的作用,方法就是代理類和委託類實現相同的介面。

開閉原則,增加功能:代理類除了是委託類的中介之外,還可以通過給代理類增加額外的功能來擴充套件委託類的功能,這樣做我們只需要修改代理類而不需要再修改委託類,符合程式碼設計的開閉原則。代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後對返回結果進行處理等。代理類本身並不真正實現服務,而是同過呼叫委託類的相關方法,來提供特定的服務。真正的業務功能還是由委託類來實現,但是可以在業務功能執行的前後加入一些公共服務。例如,我們想給專案加入快取、日誌等功能,我們就可以使用代理類來完成,而沒必要開啟已經封裝好的委託類。

代理模式可以分為靜態代理和動態代理。靜態代理是由程式設計師建立或特定工具自動生成原始碼,在對其編譯。在程式設計師執行之前,代理類.class檔案就已經被建立了。動態代理是在程式執行時通過反射機制動態建立的。動態代理可以分為jdk動態代理和cglib動態代理。

1、靜態代理

主題介面,具體類和代理類都需要實現的介面,裡面的介面方法是要被代理的方法。如下:

//主題介面
public interface Subject {
    /**
     * 介面方法
     */
    public void request();
}

具體的實現類,是要被代理的類。如下:

//具體實現類
public
class Concrete implements Subject { /** * 具體的業務邏輯實現 */ @Override public void request() { //業務處理邏輯 } }

代理類,相當於中介。如下:

//代理類
public class Proxy implements Subject {

    /**
     * 要代理的實現類
     */
    private Subject subject = null;

    public Proxy(Subject subject) {
        this.subject = subject;
    }

    /**
     * 實現介面方法
     */
    @Override
    public void request() {
        log.info("其他業務處理");
        
        this.subject.request();
        
        log.info("其他業務處理");
    }
}

測試類,客戶端呼叫類。如下:

public class Client {
    public static void main(String[] args) {
        Subject subject = new ConcreteSubject();
        Proxy proxy = new Proxy(subject);
        proxy.request();
    }
}

2、JDK動態代理

定義一個和代理類相關聯的InvacationHandler,如下:

//定義和代理類相關聯的InvacationHandler
public class ProxyInvocationHandler<T> implements InvocationHandler
{
    //invocationHandler持有的被代理物件
    T target;
    
    public ProxyInvocationHandler(T target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        log.info("其他業務處理");

        Object result = method.invoke(target, args);
        
        return result;
    }
}

測試類,客戶端呼叫類。如下:

//呼叫測試類
public class Client {
    public static void main(String[] args) {
        Subject subject = new ConcreteSubject();
        
        //建立一個與代理物件相關聯的InvocationHandler
        InvocationHandler invocationHandler = new ProxyInvocationHandler<Subject>(subject);
        
        //建立一個代理物件proxy,代理物件的每個執行方法都會替換執行Invocation中的invoke方法
        Subject proxy = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class<?>[]{Subject.class}, invocationHandler);
        
        //執行方法代理方法
        proxy.request();
    }
}

3、cglib動態代理

JDK實現動態代理需要實現類通過介面定義業務方法,對於沒有介面的類,如何實現動態代理呢,這就需要CGLib了。CGLib採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。但因為採用的是繼承,所以不能對final修飾的類進行代理。JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。

定義代理類,如下:

public class CglibProxy implements MethodInterceptor {

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

    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        log.info("其他業務處理");
        Object result = methodProxy.invoke(object, args);

        return result;
    }
}

定義測試類,如下:

public class CglibProxyTest {
    public static void main(String[] args){
    
        Subject subject = new ConcreteSubject();
        
        CglibProxy cglibProxy = new CglibProxy();
        
        Subject subjectProxy = (Subject) cglibProxy.getInstance(subject);
        
        subjectProxy.request();
    }
}

CGLIB建立的動態代理物件比JDK建立的動態代理物件的效能更高,但是CGLIB建立代理物件時所花費的時間卻比JDK多得多。所以對於單例的物件,因為無需頻繁建立物件,用CGLIB合適,反之使用JDK方式要更為合適一些。同時由於CGLib由於是採用動態建立子類的方法,對於final修飾的方法無法進行代理。

三、總結

代理解決的問題:當兩個類需要通訊時,引入第三方代理類,將兩個類的關係解耦,讓我們只瞭解代理類即可。代理模式還可以讓我們完成與另一個類之間的關係的統一管理。但是切記,代理類和委託類要實現相同的介面,因為代理真正呼叫的還是委託類的方法。

代理模式使用了程式設計中的一個思想:不要隨意去修改別人已經寫好的程式碼或者方法,如果需改修改,可以通過代理的方式來擴充套件該方法。

參考資料:

1、https://www.cnblogs.com/leeego-123/p/10995975.html

2、https://www.cnblogs.com/daniels/p/8242592.html