(轉(zhuǎn)載)Java設(shè)計(jì)模式之代理模式

volatile_logo

設(shè)計(jì)模式是語言的表達(dá)方式,它能讓語言輕便而富有內(nèi)涵、易讀卻功能強(qiáng)大。代理模式在Java中十分常見,有為擴(kuò)展某些類的功能而使用靜態(tài)代理,也有如Spring實(shí)現(xiàn)AOP而使用動(dòng)態(tài)代理,更有RPC實(shí)現(xiàn)中使用的調(diào)用端調(diào)用的代理服務(wù)。代理模型除了是一種設(shè)計(jì)模式之外,它更是一種思維,所以探討并深入理解這種模型是非常有必要的。


本文轉(zhuǎn)載自

  • Java的三種代理模式

  • 淺析JAVA設(shè)計(jì)模式之代理模式(七)

    代理(Proxy)是一種設(shè)計(jì)模式,提供了對(duì)目標(biāo)對(duì)象另外的訪問方式;即通過代理對(duì)象訪問目標(biāo)對(duì)象.這樣做的好處是:可以在目標(biāo)對(duì)象實(shí)現(xiàn)的基礎(chǔ)上,增強(qiáng)額外的功能操作,即擴(kuò)展目標(biāo)對(duì)象的功能.這里使用到編程中的一個(gè)思想:不要隨意去修改別人已經(jīng)寫好的代碼或者方法,如果需改修改,可以通過代理的方式來擴(kuò)展該方法。

    代理模式的關(guān)鍵點(diǎn)是:代理對(duì)象與目標(biāo)對(duì)象.代理對(duì)象是對(duì)目標(biāo)對(duì)象的擴(kuò)展,并會(huì)調(diào)用目標(biāo)對(duì)象

    代理的實(shí)現(xiàn)可以分為靜態(tài)代理和動(dòng)態(tài)代理,動(dòng)態(tài)代理又分為JDK動(dòng)態(tài)代理和CGlib動(dòng)態(tài)代理,下面我們依次來說明一下這三種方式:

一、靜態(tài)代理

靜態(tài)代理在使用時(shí),需要定義接口或者父類,被代理對(duì)象與代理對(duì)象一起實(shí)現(xiàn)相同的接口或者是繼承相同父類.

下面舉個(gè)案例來解釋:
模擬保存動(dòng)作,定義一個(gè)保存動(dòng)作的接口:IUserDao.java,然后目標(biāo)對(duì)象實(shí)現(xiàn)這個(gè)接口的方法UserDao.java,此時(shí)如果使用靜態(tài)代理方式,就需要在代理對(duì)象(UserDaoProxy.java)中也實(shí)現(xiàn)IUserDao接口.調(diào)用的時(shí)候通過調(diào)用代理對(duì)象的方法來調(diào)用目標(biāo)對(duì)象.
需要注意的是,代理對(duì)象與目標(biāo)對(duì)象要實(shí)現(xiàn)相同的接口,然后通過調(diào)用相同的方法來調(diào)用目標(biāo)對(duì)象的方法.
代碼示例:
接口:IUserDao.java

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

    void save();
}

目標(biāo)對(duì)象:UserDao.java

/**
 * 接口實(shí)現(xiàn)
 * 目標(biāo)對(duì)象
 */
public class UserDao implements IUserDao {
    public void save() {
        System.out.println("----已經(jīng)保存數(shù)據(jù)!----");
    }
}

代理對(duì)象:UserDaoProxy.java

/**
 * 代理對(duì)象,靜態(tài)代理
 */
public class UserDaoProxy implements IUserDao{
    //接收保存目標(biāo)對(duì)象
    private IUserDao target;
    public UserDaoProxy(IUserDao target){
        this.target=target;
    }

    public void save() {
        System.out.println("開始事務(wù)...");
        target.save();//執(zhí)行目標(biāo)對(duì)象的方法
        System.out.println("提交事務(wù)...");
    }
}

測(cè)試類:App.java

/**
 * 測(cè)試類
 */
public class App {
    public static void main(String[] args) {
        //目標(biāo)對(duì)象
        UserDao target = new UserDao();

        //代理對(duì)象,把目標(biāo)對(duì)象傳給代理對(duì)象,建立代理關(guān)系
        UserDaoProxy proxy = new UserDaoProxy(target);

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

靜態(tài)代理總結(jié):
1.可以做到在不修改目標(biāo)對(duì)象的功能前提下,對(duì)目標(biāo)功能擴(kuò)展.
2.缺點(diǎn):
因?yàn)榇韺?duì)象需要與目標(biāo)對(duì)象實(shí)現(xiàn)一樣的接口,所以會(huì)有很多代理類,類太多.同時(shí),一旦接口增加方法,目標(biāo)對(duì)象與代理對(duì)象都要維護(hù).

如何解決靜態(tài)代理中的缺點(diǎn)呢?答案是可以使用動(dòng)態(tài)代理方式.

二、JDK動(dòng)態(tài)代理

動(dòng)態(tài)代理有以下特點(diǎn):
1.代理對(duì)象,不需要實(shí)現(xiàn)接口
2.代理對(duì)象的生成,是利用JDK的API,動(dòng)態(tài)的在內(nèi)存中構(gòu)建代理對(duì)象(需要我們指定創(chuàng)建代理對(duì)象/目標(biāo)對(duì)象實(shí)現(xiàn)的接口的類型)
3.動(dòng)態(tài)代理也叫做:JDK代理,接口代理

JDK中生成代理對(duì)象的API
代理類所在包:java.lang.reflect.Proxy
JDK實(shí)現(xiàn)代理只需要使用newProxyInstance方法,但是該方法需要接收三個(gè)參數(shù),完整的寫法是:

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

注意該方法是在Proxy類中是靜態(tài)方法,且接收的三個(gè)參數(shù)依次為:

  • ClassLoader loader,:指定當(dāng)前目標(biāo)對(duì)象使用類加載器,獲取加載器的方法是固定的
  • Class<?>[] interfaces,:目標(biāo)對(duì)象實(shí)現(xiàn)的接口的類型,使用泛型方式確認(rèn)類型
  • InvocationHandler h:事件處理,執(zhí)行目標(biāo)對(duì)象的方法時(shí),會(huì)觸發(fā)事件處理器的方法,會(huì)把當(dāng)前執(zhí)行目標(biāo)對(duì)象的方法作為參數(shù)傳入
    代碼示例:
    接口類IUserDao.java以及接口實(shí)現(xiàn)類,目標(biāo)對(duì)象UserDao是一樣的,沒有做修改.在這個(gè)基礎(chǔ)上,增加一個(gè)代理工廠類(ProxyFactory.java),將代理類寫在這個(gè)地方,然后在測(cè)試類(需要使用到代理的代碼)中先建立目標(biāo)對(duì)象和代理對(duì)象的聯(lián)系,然后代用代理對(duì)象的中同名方法

代理工廠類:ProxyFactory.java

/**
 * 創(chuàng)建動(dòng)態(tài)代理對(duì)象
 * 動(dòng)態(tài)代理不需要實(shí)現(xiàn)接口,但是需要指定接口類型
 */
public class ProxyFactory{

    //維護(hù)一個(gè)目標(biāo)對(duì)象
    private Object target;
    public ProxyFactory(Object target){
        this.target=target;
    }

   //給目標(biāo)對(duì)象生成代理對(duì)象
    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("開始事務(wù)2");
                        //執(zhí)行目標(biāo)對(duì)象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("提交事務(wù)2");
                        return returnValue;
                    }
                }
        );
    }

}

測(cè)試類:App.java

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

        // 給目標(biāo)對(duì)象,創(chuàng)建代理對(duì)象
        IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
        // class $Proxy0   內(nèi)存中動(dòng)態(tài)生成的代理對(duì)象
        System.out.println(proxy.getClass());

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

總結(jié):
代理對(duì)象不需要實(shí)現(xiàn)接口,但是目標(biāo)對(duì)象一定要實(shí)現(xiàn)接口,否則不能用動(dòng)態(tài)代理

二、CGLIB代理

CGLIB概述

上面的靜態(tài)代理和動(dòng)態(tài)代理模式都是要求目標(biāo)對(duì)象是實(shí)現(xiàn)一個(gè)接口的目標(biāo)對(duì)象,但是有時(shí)候目標(biāo)對(duì)象只是一個(gè)單獨(dú)的對(duì)象,并沒有實(shí)現(xiàn)任何的接口,這個(gè)時(shí)候就可以使用以目標(biāo)對(duì)象子類的方式類實(shí)現(xiàn)代理,這種方法就叫做:Cglib代理

CGLIB也提供了一個(gè)處理器接口(這里成為回調(diào)接口)net.sf.cglib.proxy.MethodInterceptor(相當(dāng)于JDK代理的InvocationHandler接口),它自定義了一個(gè)intercept方法(相當(dāng)于JDK代理的invoke方法),用于集中處理在動(dòng)態(tài)代理類對(duì)象上的方法調(diào)用,通常在該方法中實(shí)現(xiàn)對(duì)委托類的代理訪問。

        // 該方法負(fù)責(zé)集中處理動(dòng)態(tài)代理類上的所有方法調(diào)用。第一個(gè)參數(shù)是代理類對(duì)象,第二個(gè)參數(shù)是委托類被調(diào)用的方法的類對(duì)象
       // 第三個(gè)是該方法的參數(shù),第四個(gè)是生成在代理類里面,除了方法名不同,其他都和被代理方法一樣的(參數(shù),方法體里面的東西)一個(gè)方法的類對(duì)象。
       public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodproxy) throws Throwable

然后CGLIB也提供了一個(gè)生成代理類的類net.sf.cglib.proxy.Enhancer(相當(dāng)于JDK代理的java.lang.reflect.Proxy),它提供了一組靜態(tài)方法來為一組接口動(dòng)態(tài)地生成代理類及其對(duì)象。本文創(chuàng)建代理用到的Enhancer的靜態(tài)方法如下:

public Object intercept(Object proxy,Method method, Object[] args,  MethodProxy methodproxy) throws Throwable
       //為指定類裝載器、接口及調(diào)用處理器生成動(dòng)態(tài)代理類實(shí)例
       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){}

這個(gè)create方法相當(dāng)于JDK代理的newProxyInstance方法,該方法的參數(shù)具體含義如下:

  • Class class:指定一個(gè)被代理類的類對(duì)象。
  • Class[]interfaces:如果要代理接口,指定一組被代理類實(shí)現(xiàn)的所有接口的類對(duì)象。
  • Callback h:指定一個(gè)實(shí)現(xiàn)了處理器接口(這里稱回調(diào)接口)的對(duì)象。
  • CallbackFilter filter:指定一個(gè)方法過濾器。
  • Callback[]hs:指定一組實(shí)現(xiàn)了處理器接口的對(duì)象。
  • Class[] argumentTypes:指定某個(gè)構(gòu)造器的參數(shù)類型
  • Object[] arguments:指定某個(gè)gouz

如何使用?

  1. 通過實(shí)現(xiàn) MethodInterceptor接口創(chuàng)建自己的處理器;
  2. 通過給Enhancer類的create()方法傳進(jìn)被代理類的類對(duì)象、實(shí)現(xiàn)了MethodInterceptor接口的處理器對(duì)象,得到一個(gè)代理類對(duì) 象。
  3. 其中Enhancer類通過反射機(jī)制獲得代理類的構(gòu)造函數(shù),有一個(gè)參數(shù)類型是處理器接口類型。
  4. Enhancer類再通過構(gòu)造函數(shù)對(duì)象創(chuàng)建動(dòng)態(tài)代理類實(shí)例,構(gòu)造時(shí)處理器對(duì)象作為參數(shù)被傳入。
  5. 當(dāng)客戶端調(diào)用了代理類的方法時(shí),該方法調(diào)用了處理器的intercept()方法并給intercept()方法傳進(jìn)委托類方法的類對(duì)象,intercept方法再調(diào)用委托類的真實(shí)方法。
    (1)建一個(gè)reallyCglibProxy包,所有程序都放在該包下,我在Spring的包庫里面找到兩個(gè)包:asm.jar和cblib-2.1.3.jar,最好在Spring里面同時(shí)拷貝這兩個(gè)包到項(xiàng)目的類庫下,不要分別從網(wǎng)上下載,可能會(huì)沖突。
    (2)建一個(gè)被代理類(RealSubject.java)。
package reallyCglibProxy;
//被代理類
public class RealSubject {
    public void print() {
        System.out.println("被代理的人郭襄");
    }
}

(3)建一個(gè)用戶自定義的處理器類LogHandler.java,需要實(shí)現(xiàn)處理接口,在intercept()方法里寫上被代理類的方法調(diào)用前后要進(jìn)行的動(dòng)作。這個(gè)intercept()方法我們不用直接調(diào)用,是讓將來自動(dòng)生成的代理類去調(diào)用的。

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;
 
//相當(dāng)于實(shí)現(xiàn)jdk的InvocationHandler接口
public class LogHandler implements MethodInterceptor{
    private Object delegate; //被代理類的對(duì)象
    //綁定被代理類的對(duì)象
    public Object bind(Object delegate)throws Exception{
        this.delegate=delegate;
    return Enhancer.create(delegate.getClass(),this);
    }  
    //相當(dāng)于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("我是代理人郭靖,代理完畢");
 
        //調(diào)用工具類反射jdk的Proxy生成的代理類,可參考《五》中這個(gè)工具類
        LonHanderReflectTool.ReflectClass(proxy.getClass().getName());
        return result;
    }
}

(4)編寫測(cè)試客戶端(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();
    }
}

輸出結(jié)果:

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

被代理的人郭襄

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

從結(jié)果可以看出,成功自動(dòng)生成了代理類RealSubjectEnhancerByCGLIB48574fb2,并成功實(shí)現(xiàn)了代理的效果,而且還代理了RealSubject類從父類繼承的finalize、equals、toString、hashCode、clone這幾個(gè)方法。

CBLIB方法過濾器

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

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

(2)新建一個(gè)用戶自定義的方法過濾器類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) {
 
    /**
    *這句話可發(fā)現(xiàn),所有被代理的方法都會(huì)被過濾一次。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這幾個(gè)方法)都被調(diào)用accept方法進(jìn)行過濾,給每個(gè)方法返回一個(gè)整數(shù),
    本例子是0或者1,從而選擇不同的處理器。
    */
 
System.out.println(method.getDeclaringClass()+"類的"+method.getName()+"方法被檢查過濾!");
 
    /* 
    如果調(diào)用是print方法,則要調(diào)用0號(hào)位的攔截器去處理
         */
 
    if("print".equals(method.getName()))   
 
    //0號(hào)位即LogHandler里面 new Callback[]{this,NoOp.INSTANCE}中的this,它在這個(gè)數(shù)組第0位置
 
return 1; 
 
    /*     
      1號(hào)位即LogHandler里面 new Callback[]{this,NoOp.INSTANCE}中的NoOp.INSTANCE,它在這個(gè)數(shù)組第1位置。
    NoOp.INSTANCE是指不做任何事情的攔截器。
在這里就是任何人都有權(quán)限訪問print方法,即這個(gè)方法沒有代理,直接調(diào)用
     */
 
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;
//相當(dāng)于實(shí)現(xiàn)jdk的InvocationHandler接口
public class LogHandler implements MethodInterceptor{
    private Object delegate; //被代理類的對(duì)象
    //綁定被代理類的對(duì)象
    public Object bind(Object delegate)throws Exception{
        this.delegate=delegate;
    /**
     *傳進(jìn)不同的攔截器(相當(dāng)于JDK代理里面的處理器),NoOp.INSTANCE是cglib已經(jīng)寫好的不做任何事情的攔截器,傳進(jìn)去的new Callback[]是一個(gè)數(shù)組,現(xiàn)在數(shù)組有兩個(gè)攔截器對(duì)象,分別是this,和NoOp.INSTANCE,它們分別在數(shù)組的第0位和第一位對(duì)應(yīng)著自定義過濾器MyProxyFilter里的accept方法返回的整數(shù),如果是0就調(diào)用this攔截器,如果是1就調(diào)用NoOp.INSTANCE所以自定義過濾器MyProxyFilter里的accept方法返回的整數(shù)最大就是攔截器數(shù)組的長(zhǎng)度,比如本例子當(dāng)中,只能是0或者1,不能是2,因?yàn)檫@個(gè)數(shù)組只有兩個(gè)元素,最大位置就是1號(hào)位。
     */
    return Enhancer.create(delegate.getClass(), null, new MyProxyFilter(), new Callback[]{this,NoOp.INSTANCE}); 
    }  
    //相當(dāng)于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("我是代理人郭靖,代理完畢");
 
        //調(diào)用工具類反射jdk的Proxy生成的代理類,可參考《五》中這個(gè)工具類
        //LonHanderReflectTool.ReflectClass(proxy.getClass().getName());
        return result;
    }
}

(4) 修改測(cè)試客戶端(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();
    }
}

輸出結(jié)果:

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方法哦

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

參考文章


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

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容