(轉載)Java設計模式之代理模式

volatile_logo

設計模式是語言的表達方式,它能讓語言輕便而富有內涵、易讀卻功能強大。代理模式在Java中十分常見,有爲擴展某些類的功能而使用靜態代理,也有如Spring實現AOP而使用動態代理,更有RPC實現中使用的調用端調用的代理服務。代理模型除了是一種設計模式之外,它更是一種思維,所以探討並深入理解這種模型是非常有必要的。


本文轉載自

  • Java的三種代理模式
  • 淺析JAVA設計模式之代理模式(七)

    代理(Proxy)是一種設計模式,提供了對目標對象另外的訪問方式;即通過代理對象訪問目標對象.這樣做的好處是:可以在目標對象實現的基礎上,增強額外的功能操作,即擴展目標對象的功能.這裏使用到編程中的一個思想:不要隨意去修改別人已經寫好的代碼或者方法,如果需改修改,可以通過代理的方式來擴展該方法。

    代理模式的關鍵點是:代理對象與目標對象.代理對象是對目標對象的擴展,並會調用目標對象

    代理的實現可以分爲靜態代理和動態代理,動態代理又分爲JDK動態代理和CGlib動態代理,下面我們依次來說明一下這三種方式:

一、靜態代理

靜態代理在使用時,需要定義接口或者父類,被代理對象與代理對象一起實現相同的接口或者是繼承相同父類.

下面舉個案例來解釋:
模擬保存動作,定義一個保存動作的接口:IUserDao.java,然後目標對象實現這個接口的方法UserDao.java,此時如果使用靜態代理方式,就需要在代理對象(UserDaoProxy.java)中也實現IUserDao接口.調用的時候通過調用代理對象的方法來調用目標對象.
需要注意的是,代理對象與目標對象要實現相同的接口,然後通過調用相同的方法來調用目標對象的方法.
代碼示例:
接口:IUserDao.java

/**
 * 接口
 */
public interface IUserDao {

    void save();
}

目標對象:UserDao.java

/**
 * 接口實現
 * 目標對象
 */
public class UserDao implements IUserDao {
    public void save() {
        System.out.println("----已經保存數據!----");
    }
}

代理對象:UserDaoProxy.java

/**
 * 代理對象,靜態代理
 */
public class UserDaoProxy implements IUserDao{
    //接收保存目標對象
    private IUserDao target;
    public UserDaoProxy(IUserDao target){
        this.target=target;
    }

    public void save() {
        System.out.println("開始事務...");
        target.save();//執行目標對象的方法
        System.out.println("提交事務...");
    }
}

測試類:App.java

/**
 * 測試類
 */
public class App {
    public static void main(String[] args) {
        //目標對象
        UserDao target = new UserDao();

        //代理對象,把目標對象傳給代理對象,建立代理關係
        UserDaoProxy proxy = new UserDaoProxy(target);

        proxy.save();//執行的是代理的方法
    }
}

靜態代理總結:
1.可以做到在不修改目標對象的功能前提下,對目標功能擴展.
2.缺點:
因爲代理對象需要與目標對象實現一樣的接口,所以會有很多代理類,類太多.同時,一旦接口增加方法,目標對象與代理對象都要維護.

如何解決靜態代理中的缺點呢?答案是可以使用動態代理方式.

二、JDK動態代理

動態代理有以下特點:
1.代理對象,不需要實現接口
2.代理對象的生成,是利用JDK的API,動態的在內存中構建代理對象(需要我們指定創建代理對象/目標對象實現的接口的類型)
3.動態代理也叫做:JDK代理,接口代理

JDK中生成代理對象的API
代理類所在包:java.lang.reflect.Proxy
JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個參數,完整的寫法是:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )

注意該方法是在Proxy類中是靜態方法,且接收的三個參數依次爲:

  • ClassLoader loader,:指定當前目標對象使用類加載器,獲取加載器的方法是固定的
  • Class
/**
 * 創建動態代理對象
 * 動態代理不需要實現接口,但是需要指定接口類型
 */
public class ProxyFactory{

    //維護一個目標對象
    private Object target;
    public ProxyFactory(Object target){
        this.target=target;
    }

   //給目標對象生成代理對象
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("開始事務2");
                        //執行目標對象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("提交事務2");
                        return returnValue;
                    }
                }
        );
    }

}

測試類:App.java

/**
 * 測試類
 */
public class App {
    public static void main(String[] args) {
        // 目標對象
        IUserDao target = new UserDao();
        // 【原始的類型 class cn.itcast.b_dynamic.UserDao】
        System.out.println(target.getClass());

        // 給目標對象,創建代理對象
        IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
        // class $Proxy0   內存中動態生成的代理對象
        System.out.println(proxy.getClass());

        // 執行方法   【代理對象】
        proxy.save();
    }
}

總結:
代理對象不需要實現接口,但是目標對象一定要實現接口,否則不能用動態代理

二、CGLIB代理

CGLIB概述

上面的靜態代理和動態代理模式都是要求目標對象是實現一個接口的目標對象,但是有時候目標對象只是一個單獨的對象,並沒有實現任何的接口,這個時候就可以使用以目標對象子類的方式類實現代理,這種方法就叫做:Cglib代理

CGLIB也提供了一個處理器接口(這裏成爲回調接口)net.sf.cglib.proxy.MethodInterceptor(相當於JDK代理的InvocationHandler接口),它自定義了一個intercept方法(相當於JDK代理的invoke方法),用於集中處理在動態代理類對象上的方法調用,通常在該方法中實現對委託類的代理訪問。

        // 該方法負責集中處理動態代理類上的所有方法調用。第一個參數是代理類對象,第二個參數是委託類被調用的方法的類對象
       // 第三個是該方法的參數,第四個是生成在代理類裏面,除了方法名不同,其他都和被代理方法一樣的(參數,方法體裏面的東西)一個方法的類對象。
       public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodproxy) throws Throwable

然後CGLIB也提供了一個生成代理類的類net.sf.cglib.proxy.Enhancer(相當於JDK代理的java.lang.reflect.Proxy),它提供了一組靜態方法來爲一組接口動態地生成代理類及其對象。本文創建代理用到的Enhancer的靜態方法如下:

public Object intercept(Object proxy,Method method, Object[] args,  MethodProxy methodproxy) throws Throwable
       //爲指定類裝載器、接口及調用處理器生成動態代理類實例
       public static  Object create(Class class ,Callback callback h){}
       public static  Object create(Class class,Class[] interfaces,Callback h){}
       public static Object create(Classclass, Class[] interfaces, CallbackFilter filter, Callback[] hs)
       public Object create(Class[] argumentTypes, Object[] arguments){}

這個create方法相當於JDK代理的newProxyInstance方法,該方法的參數具體含義如下:

  • Class class:指定一個被代理類的類對象。
  • Class[]interfaces:如果要代理接口,指定一組被代理類實現的所有接口的類對象。
  • Callback h:指定一個實現了處理器接口(這裏稱回調接口)的對象。
  • CallbackFilter filter:指定一個方法過濾器。
  • Callback[]hs:指定一組實現了處理器接口的對象。
  • Class[] argumentTypes:指定某個構造器的參數類型
  • Object[] arguments:指定某個gouz

如何使用?

  1. 通過實現 MethodInterceptor接口創建自己的處理器;
  2. 通過給Enhancer類的create()方法傳進被代理類的類對象、實現了MethodInterceptor接口的處理器對象,得到一個代理類對 象。
  3. 其中Enhancer類通過反射機制獲得代理類的構造函數,有一個參數類型是處理器接口類型。
  4. Enhancer類再通過構造函數對象創建動態代理類實例,構造時處理器對象作爲參數被傳入。
  5. 當客戶端調用了代理類的方法時,該方法調用了處理器的intercept()方法並給intercept()方法傳進委託類方法的類對象,intercept方法再調用委託類的真實方法。
    (1)建一個reallyCglibProxy包,所有程序都放在該包下,我在Spring的包庫裏面找到兩個包:asm.jar和cblib-2.1.3.jar,最好在Spring裏面同時拷貝這兩個包到項目的類庫下,不要分別從網上下載,可能會衝突。
    (2)建一個被代理類(RealSubject.java)。
package reallyCglibProxy;
//被代理類
public class RealSubject {
    public void print() {
        System.out.println("被代理的人郭襄");
    }
}

(3)建一個用戶自定義的處理器類LogHandler.java,需要實現處理接口,在intercept()方法裏寫上被代理類的方法調用前後要進行的動作。這個intercept()方法我們不用直接調用,是讓將來自動生成的代理類去調用的。

package reallyCglibProxy;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import jdkDynamicProxy.LonHanderReflectTool;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

//相當於實現jdk的InvocationHandler接口
public class LogHandler implements MethodInterceptor{
    private Object delegate; //被代理類的對象
    //綁定被代理類的對象
    public Object bind(Object delegate)throws Exception{
        this.delegate=delegate;
    return Enhancer.create(delegate.getClass(),this);
    }  
    //相當於InvocationHandler接口裏面的invoke()方法
    @Override
    public Object intercept(Object proxy, Method method, Object[] args,
            MethodProxy methodproxy) throws Throwable {
        Object result=null;
        System.out.println("我是代理人郭靖,開始代理");

        //method.invoke()或者methodproxy.invoke()都可以
        result=method.invoke(delegate,args);
        //result=methodproxy.invoke(delegate,args);
        System.out.println("我是代理人郭靖,代理完畢");

        //調用工具類反射jdk的Proxy生成的代理類,可參考《五》中這個工具類
        LonHanderReflectTool.ReflectClass(proxy.getClass().getName());
        return result;
    }
}

(4)編寫測試客戶端(TestReallyCglibProxy.java)。

package reallyCglibProxy;
public class TestReallyCglibProxy {
    public static void main(String[] args)throws Exception {
        RealSubject sub1=new RealSubject();
        LogHandler hander=new LogHandler();
        RealSubject sub2=(RealSubject)hander.bind(sub1);
        sub2.print();
    }
}

輸出結果:

我是代理人郭靖,開始代理

被代理的人郭襄

我是代理人郭靖,代理完畢

從結果可以看出,成功自動生成了代理類RealSubject

EnhancerByCGLIB
48574fb2,併成功實現了代理的效果,而且還代理了RealSubject類從父類繼承的finalize、equals、toString、hashCode、clone這幾個方法。

CBLIB方法過濾器

如果現在有個要求,被代理類的print方法不用處理。當最簡單的方式是修改方法攔截器(即intercept方法),在裏面進行判斷,如果是print()方法就不做任何邏輯處理,直接調用,這樣子使得編寫攔截器(相當於JDK代理裏的處理器)邏輯變複雜,不易維護。我們可以使用CGLIB提供的方法過濾器(CallbackFilter),使得被代理類中不同的方法,根據我們的邏輯要求,調用不同的處理器,從而使用不同的攔截方法(處理器方法)。
基本步驟如下:
(1)修改一下被代理類(RealSubject.java),增加一個方法print2。

package reallyCglibProxy;
//被代理類
public class RealSubject {
    public void print() {
        System.out.println("被代理的人郭襄");
    }
    public void print2() {
        System.out.println("我是print2方法哦");
    }
}

(2)新建一個用戶自定義的方法過濾器類MyProxyFilter.java。

package reallyCglibProxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Callback;

import net.sf.cglib.proxy.CallbackFilter;

import net.sf.cglib.proxy.NoOp;

//自定義方法過濾器類

public class MyProxyFilterimplements CallbackFilter {

    public int accept(Method method) {

    /**
    *這句話可發現,所有被代理的方法都會被過濾一次。Enhancer的源代碼中看到下面一段代碼
    while(it1.hasNext()){
    MethodInfomethod=(MethodInfo)it1.next();
    MethodactualMethod=(it2!=null)?(Method)it2.next():null;
    intindex=filter.accept(actualMethod);
    if(index>=callbackTypes.length){
    thrownewIllegalArgumentException("Callbackfilterreturnedanindexthatistoolarge:"+index);
    }
    上段代碼可以看到所有的被代理的方法,本例子中就是print、print2、從父類繼承的finalize、equals、toString、
    hashCode、clone這幾個方法)都被調用accept方法進行過濾,給每個方法返回一個整數,
    本例子是0或者1,從而選擇不同的處理器。
    */

System.out.println(method.getDeclaringClass()+"類的"+method.getName()+"方法被檢查過濾!");

    /* 
    如果調用是print方法,則要調用0號位的攔截器去處理
         */

    if("print".equals(method.getName()))   

    //0號位即LogHandler裏面 new Callback[]{this,NoOp.INSTANCE}中的this,它在這個數組第0位置

return 1; 

    /*     
      1號位即LogHandler裏面 new Callback[]{this,NoOp.INSTANCE}中的NoOp.INSTANCE,它在這個數組第1位置。
    NoOp.INSTANCE是指不做任何事情的攔截器。
在這裏就是任何人都有權限訪問print方法,即這個方法沒有代理,直接調用
     */

return 0;   

    }

}

(3)修改一下用戶自定義的處理器類LogHandler.java

package reallyCglibProxy;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import jdkDynamicProxy.LonHanderReflectTool;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
//相當於實現jdk的InvocationHandler接口
public class LogHandler implements MethodInterceptor{
    private Object delegate; //被代理類的對象
    //綁定被代理類的對象
    public Object bind(Object delegate)throws Exception{
        this.delegate=delegate;
    /**
     *傳進不同的攔截器(相當於JDK代理裏面的處理器),NoOp.INSTANCE是cglib已經寫好的不做任何事情的攔截器,傳進去的new Callback[]是一個數組,現在數組有兩個攔截器對象,分別是this,和NoOp.INSTANCE,它們分別在數組的第0位和第一位對應着自定義過濾器MyProxyFilter裏的accept方法返回的整數,如果是0就調用this攔截器,如果是1就調用NoOp.INSTANCE所以自定義過濾器MyProxyFilter裏的accept方法返回的整數最大就是攔截器數組的長度,比如本例子當中,只能是0或者1,不能是2,因爲這個數組只有兩個元素,最大位置就是1號位。
     */
    return Enhancer.create(delegate.getClass(), null, new MyProxyFilter(), new Callback[]{this,NoOp.INSTANCE}); 
    }  
    //相當於InvocationHandler接口裏面的invoke()方法

    public Object intercept(Object proxy, Method method, Object[] args,
            MethodProxy methodproxy) throws Throwable {
        Object result=null;
        System.out.println("我是代理人郭靖,開始代理");

        //method.invoke()或者methodproxy.invoke()都可以
        result=method.invoke(delegate,args);
        //result=methodproxy.invoke(delegate,args);
        System.out.println("我是代理人郭靖,代理完畢");

        //調用工具類反射jdk的Proxy生成的代理類,可參考《五》中這個工具類
        //LonHanderReflectTool.ReflectClass(proxy.getClass().getName());
        return result;
    }
}

(4) 修改測試客戶端(TestReallyCglibProxy.java)。

package reallyCglibProxy;
publicclass TestReallyCglibProxy {
    publicstaticvoid main(String[] args)throws Exception {
        RealSubject sub1=new RealSubject();
        LogHandler hander=new LogHandler();
        RealSubject sub2=(RealSubject)hander.bind(sub1);
        sub2.print();
        sub2.print2();
    }
}

輸出結果:

class reallyCglibProxy.RealSubject類的print2方法被檢查過濾!

class reallyCglibProxy.RealSubject類的print方法被檢查過濾!

class java.lang.Object類的finalize方法被檢查過濾!

class java.lang.Object類的equals方法被檢查過濾!

class java.lang.Object類的toString方法被檢查過濾!

class java.lang.Object類的hashCode方法被檢查過濾!

class java.lang.Object類的clone方法被檢查過濾!

被代理的人郭襄

我是代理人郭靖,開始代理

我是print2方法哦

我是代理人郭靖,代理完畢
   從結果可以看出,print方法沒有被代理,print2方法被代理了,有了方法過濾器,被代理類被代理的方法(本文例子中就是print、print2、從父類繼承的finalize、equals、toString、hashCode、clone這幾個方法)都被調用accept方法進行過濾,給每個方法返回一個整數,本例子是0或者1,從而選擇不同的處理器。

參考文章


本文作者: catalinaLi
本文鏈接: http://catalinali.top/2018/proxyPattern/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章