天天看點

Spring架構學習第二節:Java設計模式(二)2.2 動态代理模式和責任鍊模式

此部落格用于個人學習,來源于ssm架構的書籍,對知識點進行一個整理。

2.2 動态代理模式和責任鍊模式

動态代理的意義在于生成一個占位(又稱代理對象),來代理真實對象,進而控制真實對象的通路。

先舉個例子,能更好的了解代理模式。你的公司是一家軟體公司,你作為一名軟體工程師,平時的工作肯定是跟代碼打交道。客戶來你們公司,肯定不是直接找你談,而是去找商務談。此時,對于客戶來說,商務就是代表整個公司。

Spring架構學習第二節:Java設計模式(二)2.2 動态代理模式和責任鍊模式

客戶是通過商務與軟體工程師進行溝通,那麼商務(代理對象)的作用是什麼呢?商務可以進行談判,比如項目啟動前的商務談判,軟體的價格等等,商務也有可能談判失敗,此時就可以結束與客戶的合作關系,這些都不需要軟體工程師來處理。是以,代理的作用就是,在真實對象通路之前或者之後加入對應的邏輯,或者根據其他的規則控制是否使用真實對象。

我們需要在調用者調用對象之前産生一個代理對象,而這個代理對象需要和真實對象建立代理關系,是以代理分為兩個步驟:

  1. 代理對象和真實對象建立代理關系;
  2. 實作代理對象的代理邏輯方法。

Java中,代理技術由許多種,本篇主要讨論 Spring 常用的 JDK 代理技術和 CGLIB 代理技術。

2.2.1 JDK 動态代理

需要借助接口才能建立代理對象,首先定義一個 HelloWorld 接口:

public interface HelloWorld {

    public void sayHelloWorld();
}
           

然後通過 HelloWorldImpl 這個類來實作接口:

public class HelloWorldImpl implements HelloWorld{

    @Override
    public void sayHelloWorld() {
        System.out.println("Hello World!");
    }
}
           

進行動态代理:

public class JdkProxyExample implements InvocationHandler {

    //真實對象
    private Object target = null;

    /**
     * 建立代理對象與真實對象的代理關系
     * @param target 真實對象
     * @return 傳回代理對象
     */
    public Object bind(Object target){
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    /**
     * 代理方法邏輯
     * @param proxy 代理對象
     * @param method 目前排程方法
     * @param args 目前方法參數
     * @return 代理結果傳回
     * @throws Throwable 異常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("進入代理邏輯方法");
        System.out.println("在排程真實對象之前的服務");
        //相當于調用sayHelloWorld方法
        Object obj = method.invoke(target,args);
        System.out.println("在排程真實對象之後的服務");
        return obj;
    }
}
           

第一步,建立代理對象與真實對象的關系,這裡通過 bind 方法去完成,先是通過類的屬性 target 儲存了真實對象,然後通過下面的代碼建立并生成代理對象。

對 newProxyInstance 方法進行解釋:

  1. 第一個是類加載器,我們采用了 target 本身的類加載器。
  2. 第二個是把生成的動态代理對象下挂在哪些接口下,這個寫法就是放在 target 實作的接口下。
  3. 第三個是定義實作方法邏輯的代理類,this 表示目前對象,它必須實作 InvocationHandler 接口的 invoke 方法,它就是代理邏輯方法的現實方法。

第二步,實作代理邏輯方法。通過 invoke 函數實作:

  • proxy,代理對象,就是 bind 方法生成的對象。
  • method,目前排程的方法。
  • args,排程方法的參數。

這行代碼相當于排程真實對象的方法,隻是通過反射實作而已。

進行測試:

JdkProxyExample jdk = new JdkProxyExample();
//由于是挂在接口HelloWorld下,是以聲明代理對象HelloWorld proxy
HelloWorld proxy = (HelloWorld) jdk.bind(new HelloWorldImpl());
proxy.sayHelloWorld();
           

輸出結果:

進入代理邏輯方法
在排程真實對象之前的服務
Hello World!
在排程真實對象之後的服務
           

當我們建立 HelloWorld 對象的時候,就會進入代理的邏輯方法 invoke 中,因為 JDK 生成的代理類,它繼承自 Proxy 實作我們定義的 HelloWorld 接口,在實作 HelloWorld 接口方法的内部,通過反射調用了 InvocationHandlerImpl 的 invoke 方法。

2.2.2 CGLIB 動态代理

對于 JDK 動态代理技術來說,必須提供接口才能使用,在一些不能提供接口的環境中,隻能采用其他的第三方技術,比如 CGLIB 動态代理。它的優勢在于不需要提供接口,隻要一個非抽象類就能實作動态代理。

public class CglibProxyExample implements MethodInterceptor{

    /**
     * 生成 CGLIB 代理對象
     * @param cls class類
     * @return Class類的 CGLIB 代理對象
     */
    public Object getProxy(Class cls){
        //CGLIB enhancer 增強類對象
        Enhancer enhancer = new Enhancer();
        //設定增強類型
        enhancer.setSuperclass(cls);
        //定義代理邏輯對象為目前對象,要求目前對象實作 MethodInterception 方法
        enhancer.setCallback(this);
        //生成并傳回代理對象
        return enhancer.create();
    }


    /**
     * 代理邏輯方法
     * @param proxy 代理對象
     * @param method 方法
     * @param args 方法參數
     * @param methodProxy 方法代理
     * @return 代理邏輯傳回
     * @throws Throwable 異常
     */
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)throws Throwable{
        System.out.println("調用真實對象前");
        //CGLIB 反射調用真實對象方法
        Object result = methodProxy.invokeSuper(proxy,args);
        System.out.println("調用真實對象後");
        return result;
    }
}
           

這裡用了 CGLIB 的加強者 Enhancer ,通過設定超類的方法(setSuperclass),然後通過 setCallback 方法設定哪個類成為它的代理類。其中,參數為 this 就意味着是目前對象,那就要求用這個 this 這個對象實作接口 MethodInterceptor 的方法——intercept,然後傳回代理對象。在反射真實對象方法前後進行了列印,是通過如下代碼完成的。

進行測試:

public void testCGLIBProxy(){
	CglibProxyExample cpe = new CglibProxyExample();
	ReflectServiceImpl obj = (ReflectServiceImpl)cpe.getProxy(ReflectServiceImpl.class);
	obj.sayHello("張三");
}
           

CGLIB 動态代理和 JDK 動态代理是相似的,它們都是用 getProxy 方法生成代理對象,制定代理的邏輯類,而代理邏輯類要實作一個接口的一個方法,那麼這個接口定義的方法就是代理對象的邏輯方法,它可以控制真實對象的方法。

2.2.3 攔截器

設計者會為開發者設計一個攔截器供其使用,開發者隻需要知道攔截器接口發方法,含義和作用即可,無須知道動态代理是怎麼實作的。用 JDK 動态代理來實作一個攔截器的邏輯,先定義攔截器接口 Interceptor,代碼如下:

public interface Interceptor {

    public boolean before(Object proxy, Object target, Method method,Object[] args);
    public void around(Object proxy, Object target, Method method,Object[] args);
    public void after(Object proxy, Object target, Method method,Object[] args);

}
           

其中要注意的是:

  • before 方法傳回一個 boolean 值,它在真實對象前調用,當傳回為 true 時,則反射真實對象的方法;當傳回為 false 時,則調用 around 方法。
  • 在 before 方法傳回為 false 的情況下,調用 around 方法。
  • 在反射真實對象方法或者是 around 方法執行之後,調用 after 方法。

實作這個接口——MyInterceptor,代碼如下:

public class MyInterceptor implements Interceptor{
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("反射方法前邏輯");
        //不反射被代理對象的原有方法
        return false;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("反射方法後邏輯");
    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("取代了被代理對象的方法");
    }
}
           

接下來,使用 JDK 動态代理,就可以去實作這些方法在适當時的調用邏輯,此時,在 JDK 動态代理中使用攔截器。

public class InterceptorJdkProxy implements InvocationHandler {

    //真實對象
    private Object target;
    //攔截器的全限定名
    private String interceptorClass = null;

    public InterceptorJdkProxy(Object target,String interceptorClass){
        this.target = target;
        this.interceptorClass = interceptorClass;
    }

    /**
     * 綁定委托對象并傳回一個【代理占位】
     * @param target 真實對象
     * @param interceptorClass 攔截器全限定名
     * @return 代理對象【占位】
     */
    public static Object bind(Object target,String interceptorClass){
        //取得代理對象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InterceptorJdkProxy(target,interceptorClass));
    }

    /**
     * 通過代理對象調用方法,首先進入這個方法
     * @param proxy 代理對象
     * @param method 方法,被調用的方法
     * @param args 方法的參數
     * @return 代理結果傳回
     * @throws Throwable 異常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(interceptorClass == null){
            //沒有設定攔截器則直接反射原有方法
            return method.invoke(target,args);
        }
        Object result = null;
        //通過反射生成攔截器
        Interceptor interceptor = (Interceptor) Class.forName(interceptorClass).newInstance();
        //調用前置方法
        if (interceptor.before(proxy,target,method,args)){
            //反射原有對象方法
            result = method.invoke(target,args);
        }else{
            //傳回false執行around方法
            interceptor.around(proxy,target,method,args);
        }
        //調用後置方法
        interceptor.after(proxy,target,method,args);
        return result;
    }
}
           

代碼執行步驟:

  1. 在 bind 方法中使用 JDK 動态代理綁定了一個對象,然後傳回代理對象。
  2. 如果沒有設定攔截器,則直接反射真實對象的方法,然後結束,否則進行第三步。
  3. 通過反射生成攔截器,并準備使用它。
  4. 調用攔截器的 before 方法,如果傳回為 true ,反射原來的方法;否則運作攔截器的 around 方法。
  5. 調用攔截器的 after 方法。
  6. 傳回結果。
    Spring架構學習第二節:Java設計模式(二)2.2 動态代理模式和責任鍊模式
    攔截器可以進一步簡化動态代理的使用方法,使程式變得更簡單,進行測試:
public static void main(String[] args) {
        HelloWorld proxy = (HelloWorld) InterceptorJdkProxy.bind(new HelloWorldImpl(),"ssm.learn.chapter2.interceptor.MyInterceptor");
        proxy.sayHelloWorld();
    }
           

得到結果:

反射方法前邏輯
反射方法後邏輯
取代了被代理對象的方法
           

由此可見,攔截器已經生效。

2.2.4 責任鍊模式

設計者往往會用攔截器去替代動态代理,然後将攔截器的接口提供給開發者,進而簡化開發者的開發難度,但是攔截器可能有多個。舉個例子,一個程式員需要請假一周,這個時候把請假申請單看成一個對象,那麼這個對象會經過項目經理,部門經理,人事部等多個角色的審批,這個時候,每個角色都有機會可以通過攔截這個申請單進行審批或者修改。如圖所示:

Spring架構學習第二節:Java設計模式(二)2.2 動态代理模式和責任鍊模式

我們稱這種模式為責任鍊模式,它用于一個對象在多個角色中傳遞的場景,例如上面這個例子,申請單到了項目經理這裡,可以修改申請時間等資訊,然後進而影響後面的審批,即,後面的審批需要根據前面的結果進行。這個時候就可以考慮用層層代理來實作,當申請單到項目經理處,使用第一個動态代理 proxy1;當它走到部門經理處,項目經理會得到一個在項目經理的代理 proxy1 基礎上生成的 proxy2 來處理部門經理的邏輯;當它走到人事處,會在 proxy2 的基礎上生成 proxy3。如果還有其他角色,以此類推即可。

首先,定義三個攔截器:

public class Interceptor1 implements Interceptor {
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【攔截器1】的 before 方法");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {

    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【攔截器1】的 after 方法");
    }
}

public class Interceptor2 implements Interceptor {
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【攔截器2】的 before 方法");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {

    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【攔截器2】的 after 方法");
    }
}

public class Interceptor3 implements Interceptor {
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【攔截器3】的 before 方法");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {

    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【攔截器3】的 after 方法");
    }
}
           

對攔截器進行測試:

public static void main(String[] args) {
        HelloWorld proxy1 = (HelloWorld) InterceptorJdkProxy.bind(new HelloWorldImpl(),"ssm.learn.chapter2.responsibility.Interceptor1");
        HelloWorld proxy2 = (HelloWorld) InterceptorJdkProxy.bind(proxy1,"ssm.learn.chapter2.responsibility.Interceptor2");
        HelloWorld proxy3 = (HelloWorld) InterceptorJdkProxy.bind(proxy2,"ssm.learn.chapter2.responsibility.Interceptor3");
        proxy3.sayHelloWorld();
    }
           

然後就可以得到以下結果,注意觀察方法的執行順序。

【攔截器3】的 before 方法
【攔截器2】的 before 方法
【攔截器1】的 before 方法
Hello World!
【攔截器1】的 after 方法
【攔截器2】的 after 方法
【攔截器3】的 after 方法
           

可以看出,before 方法是按照從最後一個攔截器到第一個攔截器的加載順序運作,而 after 方法正好相反。

從代碼中可見,責任鍊模式的優點在于我們可以在傳遞鍊上加入新的攔截器,增加攔截邏輯,其缺點是會增加代理和反射,而代理和反射的性能不高。