当前位置: 移动技术网 > IT编程>软件设计>设计模式 > 设计模式-代理模式

设计模式-代理模式

2018年02月05日  | 移动技术网IT编程  | 我要评论
一、概念 代理模式是对象的结构模式,代理模式给某一个对象提供一个代理对象,并由这个代理对象控制对原对象的访问。 二、模式动机 比如有些对象的访问是有权限的,在访问这个对象之前需要进行权限检查,那么就给这个对象提供一个代理对象,这个代理对象对客户而言,是完全透明的,客户在使用这个代理对象时,和使用原对 ...

 一、概念

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

二、模式动机

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

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

 

三、模式的结构

    

    角色分析:

      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:      invoke( proxy,   method,  [] args) throws

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

  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方法结束

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

 五、与其它模式的关系

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

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

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网