欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

设计模式-代理模式

程序员文章站 2022-06-28 16:56:19
一、概念 代理模式是对象的结构模式,代理模式给某一个对象提供一个代理对象,并由这个代理对象控制对原对象的访问。 二、模式动机 比如有些对象的访问是有权限的,在访问这个对象之前需要进行权限检查,那么就给这个对象提供一个代理对象,这个代理对象对客户而言,是完全透明的,客户在使用这个代理对象时,和使用原对 ......

 一、概念

  代理模式是对象的结构模式,代理模式给某一个对象提供一个代理对象,并由这个代理对象控制对原对象的访问。

二、模式动机

   比如有些对象的访问是有权限的,在访问这个对象之前需要进行权限检查,那么就给这个对象提供一个代理对象,这个代理对象对客户而言,是完全透明的,客户在使用这个代理对象时,和使用原对象一模一样,没有任何区别,即客户跟本感觉不到使用的是一个原对象的代理对象。这个代理对象就起到了客户和原对象之间提供了一个间接性,在这个间接性里面,就可以进行权限判断,判断客户是否有权限调用原对象。

  还有一种虚代理的情况,如一个对像非常消耗我们的资源,如时间资源或者内存资源,但客户只是在某些情况下需要看到这个对象的所有的完整信息,而这时就可以给这个对象提供一个代理对象,该代理对象只包括原对象的部分信息,当客户需要的信息超出当前已包括的部分信息时,才对不存在的那部分信息进行加载。该对象对客户而言,和原对象没有任何区别,但是某一时刻这个代理对象可能只包括原对象的部分信息,所以它不完全等同于原对象,所以叫它虚代理对象。

 

三、模式的结构

    设计模式-代理模式

    角色分析:

      SubjectProxy:代理对象,和被代理对象实现同样的接口,这样就可以使用这个代理对象代替被代理对象。该代理对象保存一个被代理对象的引用,并控制这个被代理对象的访问。

      Subject:目标接口,代理对象和被代理对象共同的接口,这样代理对象就可以代替被代理对象。

      RealSubject:真实主题,被代理对象。

    代码示例:

 

设计模式-代理模式
package proxy;

/**
 * 目标接口,代理对象和被代理对象共同的接口,这样代理对象就可以代替被代理对象。
* @ClassName: Subject 
* @author beteman6988
* @date 2018年2月3日 上午9:07:01 
*
 */
public interface Subject {
    
    public void request();
    
}

package proxy;

/**
 * 被代理对象,实现具体的业务逻辑
* @ClassName: RealSubject 
* @author beteman6988
* @date 2018年2月3日 上午9:07:54 
*
 */
public class RealSubject implements Subject {
    
    /**
     * 具体的业务逻辑
    * @Title: request 
    * @param    
    * @return void    
    * @throws
     */
    @Override
    public void request() {
        //具体的业务逻辑
    }
}

package proxy;

/**
 * 代理对象类,里面引用真实的被代理对象
 * 
 * @ClassName: SubjectProxy
 * @author beteman6988
 * @date 2018年2月3日 上午9:10:49
 *
 */
public class SubjectProxy implements Subject {

    // 被代理对象
    private RealSubject realSubject = null;

    public SubjectProxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        // 控制判断是转调被代理对象
        realSubject.request();
    }

}

package proxy;

public class Client {

    public static void main(String[] args) {
        
        //被代理对象
        RealSubject realSubject=new RealSubject();
        
        //代理对象:代理了realSubject
        Subject subject=new SubjectProxy(realSubject);
        subject.request();
    }
}
View Code

 

四、模式样例

  静态代理:以上示例代码的模式属于静态代理模式,所谓静态代理模式,就是代理类是自已实现的,也就是代理类必须由程序员事先定义好。这种模式有一个比较不好的地方,就是当subject接口发生变化时,代理类也要跟着发生变化,不够灵活。

  动态代理:所谓动态代理,就是代理类是自已动态生成的,程序员不用事先定义代理类,也就是给定一个subject接口,就可以自动产生实现这个subject接口的代理类,这样如果接口发生了变化,代理类会跟据变化后的接口动态产生新的代理类。目前JAVA中是支持动态代理的,实现方式为通过JAVA的反射机制进行实现,下面将重点分析JAVA中动态代理的实现原理。

  JAVA实现动态代理所需要支持的类如下:

  1.java.lang.reflect.InvocationHandler:InvocationHandler 是代理实例的调用处理程序 实现的接口。每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。

   这个就是JDK的官方解释,大概意思就是,每个代理对象都要有一个关联的调用处理程序,当对这个代理对象调用方法时,代理对象就会将调用请求自动转调到调用处理程序的invoke方法。这个接口就是调用处理程序必须实现的接口。这个接口的结构如下:

    设计模式-代理模式

    它只提供了一个唯一的接口方法invoke:     Object invoke(Object proxy,  Method method,  Object[] args) throws Throwable

    我们稍后会对这个接口方法中的三个参数进行分析。

  2.java.lang.reflect.Proxy:Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。 

      这个就是JDK的官方解释,大概意思就是,Proxy类可以提供创建动态代理类的静态方法,也提供了创建代理对象的静态方法。且通过它创建的动态代理类的超类就是这个类。

  这个类的结构如下:

    设计模式-代理模式

     从上面的结构图可以看到,它对外提供了4个静态的public方法,如下:

      1.public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces)

       该方法中法的第1个参数表示类加载器,第二个参数就是subject接口集合,即通过提供接口集合产生实现subject的动态代理类,相当于代理模式中的SubjectProxy角色。

      2.public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException

       通过该方法可以产生动态代理类的一个代理对象,第1个参数表示类加载器,第二个就是subject接口集合,第三个参数表示该代理对象关联的调用处理程序。

      3.public static boolean isProxyClass(Class<?> cl)

       判断一个类是否是为动态代理类

      4.public static InvocationHandler getInvocationHandler(Object proxy)throws IllegalArgumentException

       返回一个动态代理对象的调用处理程序

       

         getProxyClass和newProxyInstance是我们要重点分析的方法:

       getProxyClass:产生一个动态代理类,首先他是一个类。我们经常看到的类都是有一个对应的.java文件,但是这个类是jdk动态产生的,我们并不能查看他产生的.java原代码是什么样,虽然看不到它具体产生的代码是什么样,但我们也可以进行一定的推测。首先这个代理类肯定implents 该方法中第二个参数提供的所有接口。

         newProxyInstance:产生一个动态代理类的一个实例,即产生一个代理对象,通过查看JDK的源代码可知,他首先通过调用getProxyClass获得产生的动态代理类,然后获得该动态代理类的构造函数,通过构造函数实例化一个动态代理对象,原代码如下:

      设计模式-代理模式

      可以看到,该方法返回了一个动态代理对象,问题来了,动态代理对象中的被代理对象在哪?没看到!有没有提供set方法?没有,那么实例化该代理对象时可否通过构造函数的参数传进去,答案是否定的。通过cons.newInstance(new Object[]{h})可以看出,为构造函数提供的是一个调用处理程序(实现了InvocationHandler接口的实例)的实例,而并非被代理对象。那么被代理对象怎么置到代理对象里面呢?大胆推测只能包含在InvocationHandler的实例里面。当对代理对象调用方法时,会首先转调到在InvocationHandler的实例的invoke方法,在inovke方法里面再将请求转调到InvocationHandler实例里面所包含的被代理对象,这样就合理了。为了证明我们推测的合理性,可以写一个模拟程序,代码如下:

 

设计模式-代理模式
package proxy.sample;

/**
 * 人类接口
 * 被代理对象类和动态代理类都要实现的接口
* @ClassName: IMan 
* @author beteman6988
* @date 2018年2月3日 下午10:07:09 
*
 */
public interface IMan {

    /**
     * 睡觉
    * @Title: sleep 
    * @param    
    * @return void    
    * @throws
     */
    public void sleep();

    /**
     * 走路
    * @Title: go 
    * @param @return   
    * @return IMan    
    * @throws
     */
    public IMan go();
}

package proxy.sample;

/**
 * 超人
 * 被代理对象类 
* @ClassName: SuperMan 
* @author beteman6988
* @date 2018年2月3日 下午10:09:16 
*
 */
public class SuperMan implements IMan {

    @Override
    public void sleep() {
        System.out.println("超人在睡觉!");
        
    }
    
    @Override
    public IMan go() {
        System.out.println("我是超人,我在飞翔!");
        return null;
    }

}
 
package proxy.sample;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 调用处理程序
 * 将从代理对象委派过来的请求,通过调用处理程序,继续委派给被代理对象
* @ClassName: ManInvocationHandler 
* @author beteman6988
* @date 2018年2月4日 上午9:33:42 
*
 */
public class ManInvocationHandler implements InvocationHandler {
    
    //被代理对象
    private Object realObj=null;
    

    public ManInvocationHandler(Object realObj) {
        this.realObj = realObj;
    }



    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //将请求委派给被代理对象,并将被代理对象的执行结果返回
        System.out.println("调用代理对象的"+method.getName()+"方法");
        Object rnt= method.invoke(realObj, args);
        System.out.println("调用代理对象的"+method.getName()+"方法结束");
        return rnt;
    }

}

package proxy.sample;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 模拟产生的动态代理类,该动态代理类实现了IMan接口,且它的超类是Proxy类
 * 
 * @ClassName: MyProxy
 * @author beteman6988
 * @date 2018年2月3日 下午10:12:04
 *
 */
public class MyProxy extends Proxy implements IMan {

    /**
     * 该类的构造函数
     *  为什么它的参数是InvocationHandler,通过Proxy.newProxyInstance方法中的
     * cons.newInstance(new Object[]{h})可以看出,该类的构造函数参数必定是InvocationHandler类型
     * 
     * @param h
     */
    protected MyProxy(InvocationHandler h) {
        /**
         * 调用父类的构造函数
         * 先实例化父类,并将调用处理程序置入到父类的protected InvocationHandler h成员
         * 从父类的protected InvocationHandler h是protected,所以proxy的子类,即当前类
         * 可以直接访问这个受保护的成员。
         */
        super(h);
    }

    @Override
    public void sleep() {
        Object obj=null;
        Method method=null;
        try {
            Class<IMan>[] interfaces=(Class<IMan>[]) this.getClass().getInterfaces();
            for(Class aInter:interfaces) {
                method=aInter.getMethod("sleep", null);
            }
             obj=super.h.invoke(this, method, null);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    /**
     * 实现IMan接口的方法
     * 该接口很重要的一个职责就是要转调调用处理程序,由调用处理程序通过invoke将请收继续转调到被代理对象,并返回被代理对象执行过后返回的结果。
     */
    @Override
    public IMan go() {
        Object obj=null;
        Method method=null;
        try {
            /**
             * 获取动态类实现的所有接口,并对每个接口进行编历,此处实现颇为简单,考虑的很少,只是模拟代码,真实的实现可能比较复杂
             */
            Class<IMan>[] interfaces=(Class<IMan>[]) this.getClass().getInterfaces();
            for(Class aInter:interfaces) {
                //获取当前方法在接口中的方法对象,并将方法对象传给调用处理程序
                method=aInter.getMethod("go", null);
            }
            /**
              * 划重点,将请求转调给调用处理程序的invoke方法
              * 第一个参数为当前代理对象,第二个参数为接口方法对象,第三个为接口方法对象的参数对象,暂且认为为null
              */
            obj=super.h.invoke(this, method, null);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return (IMan)obj;
    }

}

package proxy.sample;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {

    public static void main(String[] args) {
        //实例化被代理对象
        IMan aMan=new SuperMan();
        //将被代理对象置入到调用处理程序
        InvocationHandler handler=new ManInvocationHandler(aMan);
        //实例化代理对象,并将调用处理程序置入到代理对象
        IMan man=(IMan)new MyProxy(handler);
        //IMan man=(IMan)Proxy.newProxyInstance(SuperMan.class.getClassLoader(),SuperMan.class.getInterfaces() , handler);
        
        //请求代理对象的方法
        man.go();
        man.sleep();
    }
}
View Code

执行结果如下:

调用代理对象的go方法
我是超人,我在飞翔!
调用代理对象的go方法结束
调用代理对象的sleep方法
超人在睡觉!
调用代理对象的sleep方法结束

 

如果将Test类的Main方法进行修改,使用JAVA提供的动态代理会是什么样的情况呢?

设计模式-代理模式
package proxy.sample;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {

    public static void main(String[] args) {
        //实例化被代理对象
        IMan aMan=new SuperMan();
        //将被代理对象置入到调用处理程序
        InvocationHandler handler=new ManInvocationHandler(aMan);
        //实例化代理对象,并将调用处理程序置入到代理对象
        //IMan man=(IMan)new MyProxy(handler);
        //使用JAVA提供的动态代理
        IMan man=(IMan)Proxy.newProxyInstance(SuperMan.class.getClassLoader(),SuperMan.class.getInterfaces() , handler);
        
        //请求代理对象的方法
        man.go();
        man.sleep();
    }
}
View Code

执行结果如下:

调用代理对象的go方法
我是超人,我在飞翔!
调用代理对象的go方法结束
调用代理对象的sleep方法
超人在睡觉!
调用代理对象的sleep方法结束

  从执行结果看,我们完全模拟实现了JAVA的动态代理,即我们模拟出了代理类。并了解了代理对象如何转调调用处理程序,并通过调用处理程序将请求进一步委派给被代理对象去执行。

   时序图如下:

    设计模式-代理模式

    调用处理程序中的invoke(Object proxy, Method method, Object[] args) 第1个参数proxy,这是个什么东东?貌似代理对象?被代理对象?

    假设它是代理对象,如上例,如果它是代理对象,那么如果对这个对象继续转调go()方法,会出现什么问题?会出现死循环,为什么会出现死循环呢,程序的运行路径是这样的:代理对象go()方法---》转调调用理理程序的invoke, invoke方法里面再调用代理对象的go()方法,那么就会出现一个闭合的死循环,可以用下面的代码去验证:

设计模式-代理模式
package proxy.sample;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 调用处理程序
 * 将从代理对象委派过来的请求,通过调用处理程序,继续委派给被代理对象
* @ClassName: ManInvocationHandler 
* @author beteman6988
* @date 2018年2月4日 上午9:33:42 
*
 */
public class ManInvocationHandler implements InvocationHandler {
    
    //被代理对象
    private Object realObj=null;
    

    public ManInvocationHandler(Object realObj) {
        this.realObj = realObj;
    }



    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //将请求委派给被代理对象,并将被代理对象的执行结果返回
        System.out.println("调用代理对象的"+method.getName()+"方法");
        //Object rnt= method.invoke(realObj, args);
        Object rnt= method.invoke(proxy, args); //将请求委派给代理对象,将会出现死循环
        System.out.println("调用代理对象的"+method.getName()+"方法结束");
        return rnt;
    }

}
View Code

运行结果如下:

设计模式-代理模式

一直出现死循环,直到将内存耗尽,无论是用自已写的类或者JDK自已提*生的动态类,结果都是一样,证明我们的推断是正确的。第1个参数就是代理对象,而非被代理对象,这也就解释了,被代理对象需要被包含在调用处理程序中,且在invoke方法中,将请求必须转调到被代理对象。

  再进一步推测,如接口的go()方法返回的是当前对象,如果在invoke将传入的proxy对象返回,那么对这个返回的对象可否直接调用sleep对象呢,如man.go().sleep(); 答案是肯定的(这种测试仅限上面的用例 ,正常情况下不会这么做)。

 设计模式-代理模式

设计模式-代理模式

 (前提是代理对象的go()方法返回了invoke的结果,且将结果转换成了IMan类型,否则man.go().sleep()是不成立的。)

运行结果如下:

调用代理对象的go方法
我是超人,我在飞翔!
调用代理对象的go方法结束
调用代理对象的sleep方法
超人在睡觉!
调用代理对象的sleep方法结束

   代理模式在现实生活就还是比较多的,如红娘就是一个典型的代理对象,不过他是双向代理,在与男方沟通时他代表了女方,在与女方沟通时他又代表了男方。还有如代理律师等,在此不一一枚举。

 五、与其它模式的关系

  代理模式和对象适配器模式:这两个模式有相似性,他们都为另一个对象提供间接性的访问,二者都是从自身以外的接口向这个对象转发请求。但从功能上来讲,两个模式是不一样的。适配器模式主要是解决接口之间不匹配的问题,它通常是为所适配的对象提供一个不同的接口,而代理模式会实现和目标对象相同的接口。

  代理模和装饰模式:这两个模式从实现上相似,但是功能上是不同的。装饰模式的实现和保护代理模式的实现上类似,二者都是在转调其它对象前后执行一定的功能,但是目的不同,装饰模式的主要目的是动态的增加工能,而代理模式的主要目的是控制访问。