Java設計模式-之代理模式(動態代理)

俺就不起網名發表於2017-09-10

一、簡介

1、什麼叫代理模式:
       簡而言之就是:為其他物件提供一種代理以控制對這個物件的訪問。在某些情況下,一個物件不適合或者不能直接引用另一個物件,而代理物件可以在客戶端和目標物件之間起到中介的作用,其特徵是代理類與委託類有同樣的介面。代理模式是一種設計思想。


2、舉個例子:
a、以公司前臺為例子。你去某個公司面試,首先找前臺填表格,前臺然後再找HR,真正面試的過程是HR來實現。這裡前臺就是個代理物件,供外部呼叫,而真實物件是HR。
b、運營給產品經理提需求,程式設計師來實現效果。產品經理就是代理類,程式設計師就是被代理類,實現由程式設計師來實現。
c、代理解決的問題當兩個類需要通訊時,引入第三方代理類,將兩個類的關係解耦,讓我們只瞭解代理類即可,而且代理的出現還可以讓我們完成與另一個類之間的關係的統一管理,但是切記,代理類和委託類要實現相同的介面,因為代理真正呼叫的還是委託類的方法。


3、代理模式一般涉及到的角色有:
抽象角色:可以是介面,也可以是抽象類(真實物件和代理物件的共同呼叫)。
真實角色:代理角色所代表的真實物件,是我們最終要引用的物件,業務邏輯的具體執行者。
代理角色:內部含有真實角色的引用,負責對真實角色的呼叫,並在真實角色處理前後做預處理和善後工作。


4、代理模式優點:
職責清晰:真實角色只需關注業務邏輯的實現,非業務邏輯部分,後期通過代理類完成即可。
高擴充套件性:不管真實角色如何變化,由於介面是固定的,代理類無需做任何改動。


5、java的代理模式分為靜態代理模式和動態代理模式。


二、靜態代理

這裡以產品經理例子寫個靜態代理方法。相關程式碼如下:

/**
 * 抽象角色,實現需求
 */
public interface ICoder {
    void implDemands(String demandName);
}
/**
 * 真實角色,(程式設計師)實現真正業務
 */
public class JavaCoder implements ICoder{

    private String name;

    public JavaCoder(String name){
        this.name = name;
    }

    @Override
    public void implDemands(String demandName) {
        System.out.println(name + " implemented demand:" + demandName + " in JAVA!");
    }
}
/**
 * 代理角色 (產品經理)
 */
public class CoderProxy implements ICoder{

    private ICoder coder;

    public CoderProxy(ICoder coder){
        this.coder = coder;
    }

    @Override
    public void implDemands(String demandName) {
        if(demandName.startsWith("Add")){
            System.out.println("No longer receive 'Add' demand");
            return;
        }
        coder.implDemands(demandName);
    }

}
/**
 * 測試:產品經理實現程式設計
 */
public class Customer {
    public static void main(String args[]){
        //定義一個java碼農(真實角色)
        ICoder coder = new JavaCoder("Zhang");
        //定義一個產品經理(代理角色)
        ICoder proxy = new CoderProxy(coder);
        //讓產品經理實現一個需求
        proxy.implDemands("Add user manageMent");
    }
}

產品經理充當了程式設計師的代理,運營把需求告訴產品經理,並不需要和程式設計師接觸。產品經理當然不只是向程式設計師轉達需求,他還有很多事情可以做。比如,該專案決定不接受新增功能的需求了,對修CoderProxy類方法做一些開始前的檢查。
關鍵點:真實角色和代理角色,都實現抽象角色的介面,然後代理角色呼叫真實角色的實現介面。


為啥要用動態代理?
靜態代理的缺點顯而易見:
1、代理類和委託類實現了相同的介面,代理類通過委託類實現了相同的方法。這樣就出現了大量的程式碼重複。如果介面增加一個方法,除了所有委託類需要實現這個方法外,所有代理類也需要實現此方法。增加了程式碼維護的複雜度。
2、代理物件只服務於一種型別的物件,如果要服務多型別的物件。勢必要為每一種物件都進行代理,靜態代理在程式規模稍大時就無法勝任了。如上的程式碼是隻為JavaCoder類的訪問提供了代理,但是如果還要為其他類如PhpCoder類提供代理的話,就需要我們再次新增代理類。

舉例說明:假設有這麼一個需求,在方法執行前和執行完成後,列印系統時間。這是非業務邏輯,只要在代理類呼叫真實角色的方法前、後輸出時間就可以了。像上例,只有一個implDemands方法,這樣實現沒有問題。但如果真實角色有10個方法,那麼我們要寫10遍完全相同的程式碼。那麼這裡我們就需要用動態代理方法了。

三、動態代理

代理類在程式執行時建立的代理方式被稱為動態代理。也就是說,代理類並不需要在Java程式碼中定義,而是在執行時動態生成的。

現在我們利用列印日誌的例子來演示一個動態代理。與靜態代理相比,抽象角色、真實角色都沒有變化。變化的只有代理類。因此,抽象角色、真實角色分別對應ICoder和JavaCodr類內容不變。

/**
 * 定義一個位於代理類與委託類之間的中介類,也叫動態代理類
 * 要求實現InvocationHandler介面
 */
public class CoderDynamicProxy implements InvocationHandler {

    //被代理的例項
    private ICoder coder;
    public CoderDynamicProxy(ICoder _coder){
        this.coder = _coder;
    }

    //呼叫被代理的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(System.currentTimeMillis());
        Object result = method.invoke(coder, args);
        System.out.println(System.currentTimeMillis());
        return result;
    }
}
/**
 * 模擬使用者找產品經理更改需求
 */
public class DynamicClient {
    public static void main(String args[]){
        //要代理的真實物件
        ICoder coder = new JavaCoder("Zhang");
        //建立中介類例項
        InvocationHandler handler = new CoderDynamicProxy(coder);
        //獲取類載入器
        ClassLoader cl = coder.getClass().getClassLoader();
        //動態產生一個代理類
        ICoder proxy = (ICoder) Proxy.newProxyInstance(cl, coder.getClass().getInterfaces(), handler);
        //通過代理類,執行doSomething方法;
        proxy.implDemands("Modify user management");
    }
}

Proxy.newProxyInstance三個引數分別為:類載入器、代理例項介面、動態中介物件。

執行結果如下:
這裡寫圖片描述

通過上述程式碼,就實現了,在執行委託類的所有方法前、後列印時間。還是那個熟悉的小張,但我們並沒有建立代理類,也沒有時間ICoder介面。這就是動態代理。


一個典型的動態代理可分為以下四個步驟:
建立抽象角色
建立真實角色
通過實現InvocationHandler介面建立中介類
通過場景類,動態生成代理類( Proxy.newProxyInstance(cl, coder.getClass().getInterfaces(), handler))

三、動態代理的兩種實現方式

兩種方式:jdk動態代理和cglib動態代理

1、jdk動態代理
如上面的例子就是一個jdk動態代理的方法。
jdk動態代理是由java內部的反射機制來實現的。 JDK動態代理所用到的代理類在程式呼叫到代理類物件時才由JVM真正建立,JVM根據傳進來的 業務實現類物件 以及 方法名 ,動態地建立了一個代理類的class檔案並被位元組碼引擎執行,然後通過該代理類物件進行方法呼叫。
JDK動態代理的代理物件在建立時,需要使用業務實現類所實現的介面作為引數(因為在後面代理方法時需要根據介面內的方法名進行呼叫)。如果業務實現類是沒有實現介面而是直接定義業務方法的話,就無法使用JDK動態代理了。並且,如果業務實現類中新增了介面中沒有的方法,這些方法是無法被代理的(因為無法被呼叫)。這是jdk動態代理的侷限性。

2、cglib框架動態代理
cglib是針對類來實現代理的,原理是對指定的業務類生成一個子類,並覆蓋其中業務方法實現代理。因為採用的是繼承,所以不能對final修飾的類進行代理。
上面的案例用該種方式實現程式碼如下:

package proxy.cglibProxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 1、首先定義業務類
 * 無需實現介面(當然,實現介面也可以,不影響的)
 */
class JavaCoderImpl {
    private String name;

    public JavaCoderImpl(String name) {
        this.name = name;
    }

    public JavaCoderImpl() {

    }

    public void implDemands(String demandName) {
        System.out.println(name + " implemented demand:" + demandName + " in JAVA!");
    }
}

/**
 * 2、實現 MethodInterceptor方法代理介面,建立代理類
 */
class JavaCoderCglib implements MethodInterceptor {

    private Object obj;//業務類物件,供代理方法中進行真正的業務方法呼叫

    //相當於JDK動態代理中的繫結
    public Object getInstance(Object target, Class[] argumentTypes, Object[] arguments) {
        this.obj = target;  //給業務物件賦值
        Enhancer enhancer = new Enhancer(); //建立加強器,用來建立動態代理類
        enhancer.setSuperclass(this.obj.getClass());  //為加強器指定要代理的業務類(即:為下面生成的代理類指定父類)
        //設定回撥:對於代理類上所有方法的呼叫,都會呼叫CallBack,而Callback則需要實現intercept()方法進行攔
        enhancer.setCallback(this);
        // 建立動態代理類物件並返回
        return enhancer.create(argumentTypes, arguments);//不帶引數則方法enhancer.create()
    }


    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println(System.currentTimeMillis());
        methodProxy.invokeSuper(obj, args); //呼叫業務類(父類中)的方法
        System.out.println(System.currentTimeMillis());
        return null;
    }

}

/**
 * 測試
 */
public class TestCglib {
    public static void main(String[] args) {
        JavaCoderImpl javaCoder = new JavaCoderImpl();//建立業務類物件
        JavaCoderCglib javaCoderCglib = new JavaCoderCglib();//
        JavaCoderImpl javaCoderProxy = (JavaCoderImpl) javaCoderCglib.getInstance(javaCoder, new Class[]{String.class}, new Object[]{"zhangsan"});//建立代理物件
        javaCoderProxy.implDemands("create proxy by cglib");
    }
}


jdk方式必須實現介面,cglib方式不能代理final修飾的類;

四、生成分析

上面的jdk生成動態代理類的場景類中,通過

//動態產生一個代理類
ICoder proxy = (ICoder) Proxy.newProxyInstance(cl, coder.getClass().getInterfaces(), handler);

動態產生了一個代理類。那麼這個代理類是如何產生的呢?我們通過程式碼一窺究竟。

Proxy類的newProxyInstance方法,主要業務邏輯如下:

//生成代理類class,並載入到jvm中
Class<?> cl = getProxyClass0(loader, interfaces);
//獲取代理類引數為InvocationHandler的建構函式
final Constructor<?> cons = cl.getConstructor(constructorParams);
//生成代理類,並返回
return newInstance(cons, ih);

上面程式碼做了三件事:

1、根據傳入的引數interfaces動態生成一個類,它實現interfaces中的介面,該例中即ICoder介面的implDemands方法。假設動態生成的類為$Proxy0。
2、通過傳入的classloder,將剛生成的$Proxy0類載入到jvm中。
3、利用中介類,呼叫$Proxy0的$Proxy0(InvocationHandler)建構函式,建立$Proxy0類的例項,其InvocationHandler屬性,為我們建立的中介類。

最後總結:
1、一個代理模式主要有幾個角色:抽象角色、具體角色(被代理角色)、代理角色、場景類(呼叫代理角色)
2、靜態代理,首先定義一個介面,然後代理角色和被代理角色都要實現這個介面,最後呼叫方呼叫代理角色的方法。缺點是都要實現介面,擴充套件性不好。
3、動態代理四步驟:建抽象角色、建真實角色、建立動態中介類(jdk或cglib動態代理)、場景類動態生成代理類
4、jdk動態代理實現介面InvocationHandler來建立動態中介類
5、cglib是針對類來實現代理的,原理是對指定的業務類生成一個子類,並覆蓋其中業務方法實現代理
6、jdk動態代理只能針對介面來生成代理類(從Proxy的介面方法newProxyInstance(classLoader,interfaces,invocationHandler)可以看出);cglib採用底層的位元組碼技術,針對類來實現代理;


參考:說說 JAVA 代理模式

相關文章