代理模式详解

来源:互联网 发布:第五代软件下载 编辑:程序博客网 时间:2024/06/10 00:09

代理模式的作用: 为其他对象提供了一个代理以控制对这个对象的访问
在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理模式可以在客户端和目标对象之间起到中介的作用。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性

Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架

代理模式涉及到的角色:
抽象角色: 声明真实对象和代理对象的共同接口

代理角色
代理对象内部含有对真实对象的引用,从而可以操作真实对象;
同时 代理对象提供与真实对象相同的接口以便在任何时候都能代替真实对象;
同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

真实角色: 代理角色所代表的真实对象,使我们最终要引用的对象

下面例子讲解一下代理模式,帮助我们理解

抽象角色:可以是一个接口或者抽象类

 // 抽象的角色,可以是一个接口或者抽象类interface AbstractProxy {     public void sayHello();}

真实角色:要实现接口或者继承抽象类

public class RealProxy implements AbstractProxy{    // 真正的角色,要实现接口或者继承抽象类    @Override    public void sayHello() {        // TODO Auto-generated method stub        System.out.println("我是真正的角色");      }}

代理角色:是代理模式中作重要的
第一: 要实现跟真实的角色一样的接口或者抽象类
第二: 拥有一个真实角色的引用
第三: 代理角色可以添加其他附加的操作,相当于对真实对象进行封装

// 代理角色(是代理模式中作重要的), // 第一: 要实现跟真实的角色一样的接口或者抽象类public class ProxyProxy implements AbstractProxy{    // 第二:  拥有一个真实角色的引用    private RealProxy   real;    @Override    public void sayHello() {        //        if (real==null){            real = new RealProxy();        }        // 第三: 代理角色可以添加其他附加的操作,相当于对真实对象进行封装        sayNo();        real.sayHello(); //真实角色完成的事情        sayYes();       }    public  void sayNo(){        System.out.println("我是代理,我要说no");    }    public void sayYes(){        System.out.println("我是代理,我要说yes");    }}

以上属于静态代理,但是静态代理有一个问题就是真实的角色必须已经存在了,并且作为代理角色的一个内部属性。但是在使用时,如果一个真实角色必须对应这一个代理角色,大量使用会导致类的急剧膨胀,此外如果事先不知道真实角色,该如何实现代理呢,java 提供了动态代理来处理这个问题。

动态代理 : 一个代理角色可以实现无数多个真实角色的代理

Dynamic Proxy (动态代理类): 是这样一种class,它是在运行时生成的class,在生成它时,你必须提供一组interface给它,然后该class就宣称它实现了这些interface,可以把该class的实例当做接口中的任何一个来使用, 但是这个动态代理其实就是一个代理,它不会替你做实质性的工作,在生成它的实例的时候你必须提供一个handler,由它接管实际的工作。

class  A implements AA, BB,  CC{public static void main(String[] args){      AA a = new A();      BB b = new A();      CC c = new A();   }}

接下来我们就详细的讲解一下动态代理的过程,
首先: 动态代理的 抽象角色 和 真实角色的定义是跟静态代理完全一致的, 区别在于代理角色和实际调用的时候的实现完全不一样

定义: 抽象角色

public  interface AbstractProxy {     public void sayHello();}

定义:真实角色

public class RealProxy implements AbstractProxy{    // 真正的角色,要实现接口或者继承抽象类    @Override    public void sayHello() {        // TODO Auto-generated method stub        System.out.println("我是真正的角色");      }}

定义动态代理角色:
第一步: 首先该类要实现 InvocationHandler 接口, 这个接口和Proxy类是实现动态代理的关键。
第二步: 首先定义一个对真实对象的引用,因为是动态代理,所以不需要指定特定的真实角色的类型,直接定义一个Object类型的成员变量
第三步: InvocationHandler 接口中存在一个invoke函数,要重写这个函数实现我们所要调用的真实角色的功能,同样的也可以附加上代理类自身定义的行为。

// 动态代理类,首先要实现 InvocationHandler 接口public class DynamicProxy implements InvocationHandler{     // 因为是动态代理,所以不需要指定特定的真实角色的类型    private  Object  object;    public  DynamicProxy( Object  object){        this.object = object;    }    // 重写  InvocationHandler 接口中的invoke 方法 ,第一个参数为真实角色的对象    // 第二个参数为 要调用的真实对象的方法, 第三个是要调用的真实对象方法的参数列表    @Override    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        // TODO Auto-generated method stub        sayNo();  // 附加的代理类的操作        // 这里的调用方法是通过反射机制实现的        method.invoke(object,args);        sayYes();  // 附加的代理类的操作        return null;    }    public  void sayNo(){        System.out.println("我是代理,我要说no");    }    public void sayYes(){        System.out.println("我是代理,我要说yes");    }}

客户端的调用: 很重要,搞明白这个,动态代理才算明白了

动态代理的作用就是我们避免了为每一个真正的角色绑定一个相对应的代理类, 实现了通过一个代理类就可以访问到多个真实的角色。

实现过程:
第一步: 首选生成一个真实角色的对象
第二步: 生成一个代理类的对象(代理类一定是实现了InvocationHandler 接口的),用一个InvocationHandler 引用去指向它, 并且获得handle类的信息。
第三步: 获得真实角色对象所实现的全部接口,并且用数组存储表示
第四步: 调用Proxy类的 newProxyInstance方法,动态的获得一个代理实例化的对象,并且将其强制转化为 接口类型的对象
第五步: 使用动态生成的代理对象调用需要调用的方法(系统内部会自动调用到真实对象的方法)
代码如下图所示。这是它的一个实现流程,接下来我们就详细的分析一下它的原理

在动态代理的过程中用到了类加载器,现在先讲一下类加载器

类加载器的作用是负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用;Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中

        // 动态代理一个完整的实现过程        RealProxy  realProxy2= new RealProxy();        InvocationHandler handler2 = new DynamicProxy(realProxy2);        Class<?> classType2 = handler2.getClass();        Class<?>[]  classInterface2 = realProxy2.getClass().getInterfaces();        AbstractProxy abstactProxy2 = (AbstractProxy) Proxy.newProxyInstance(classType2.getClassLoader(),                 classInterface2, handler2);        abstactProxy2.sayHello();

理解动态代理的难度在于这两句:

 AbstractProxy abstactProxy = (AbstractProxy)Proxy.newProxyInstance(classType.getClassLoader(),                 classInterface, handler);  // 第四步 abstactProxy.sayHello();  // 第五步             

第四步实现了运行时动态的生成一个代理对象。这个代理对象时怎么生成的呢?

首先讲解一下Proxy类的newProxyInstance方法,它有三个参数变量
第一个是InvocationHandler 对象的类加载器,
第二个是真实角色实现的全部的接口,
第三个是一个 InvocationHandler 对象(指向了一个代理对象)。

这个动态生成的代理对象有什么特点呢?
1. 它并不是真正角色的实例, 也不是代理类DynamicProxy类的实例, 这个动态的实例的类型为 $ProxyN, N是一个可以变化的数字,是在运行时产生的。
2. 生成的这个类继承自Proxy类,实现了 真实角色里面的所有接口,所以可以转换成实现的接口的类型

3. 每个实例 都会关联一个调用处理器对象,可以通过 Proxy 提供的静态方法 getInvocationHandler 去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行

那么下一个问题: 动态生成的代理对象时如何生成的呢?

  1. 首先:根据类加载器和接口构造一个动态代理类对象
  2. 其次:通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型 InvocationHandler;
  3. 通过构造函数创建动态代理类实例,构造时调用处理器对象handler作为参数被传入,实例化一个该动态代理类的一个对象
  4. 返回构造好的一个Object对象,至此动态的生成了一个实例化对象

% 别人写的动态对象生成过程,我觉的非常好
一个典型的动态代理创建对象过程可分为以下四个步骤:
1、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(…);
2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{…});
3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。

我们可以看一下newProxyInstance方法里面的源代码,结果会更加一目了然。

public static Object newProxyInstance(ClassLoader loader,             Class<?>[] interfaces,             InvocationHandler h)             throws IllegalArgumentException {     // 检查 h 不为空,否则抛异常    if (h == null) {         throw new NullPointerException();     }     // 获得与制定类装载器和一组接口相关的代理类类型对象    Class cl = getProxyClass(loader, interfaces);     // 通过反射获取构造函数对象并生成代理类实例    try {         Constructor cons = cl.getConstructor(constructorParams);         return (Object) cons.newInstance(new Object[] { h });     } catch (NoSuchMethodException e) { throw new InternalError(e.toString());     } catch (IllegalAccessException e) { throw new InternalError(e.toString());     } catch (InstantiationException e) { throw new InternalError(e.toString());     } catch (InvocationTargetException e) { throw new InternalError(e.toString());     } }

第五步也是比较难以理解的,怎么就通过这个动态生成的代理类对象就可以调用到实际对象的方法了。
在这里我们首先要讲一下 InvocationHandler 类的invoke 方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {}
有三个参数: 第一个参数为真实角色的对象, 第二个参数为 要调用的真实对象的方法, 第三个是要调用的真实对象方法的参数列表

第五步是这样书写的: 特别简单,就是调用一下要使用的真实角色的方法名。虽然只有简单的一行,但是其的内部实现是特别复杂的。
我们知道,生成的一个动态代理对象时$ProxyN类型的,但是其实现了真实对象的接口,所以可以将其强制转化为接口类型的,在下面的一句中 abstactProxy就是接口类型的。
每个动态生成的实例都会关联一个调用处理器对象,所以abstactProxy也关联着一个Handler对象(在这里Handler对象是指向了一个代理对象 DynamicProxy),当通过该实例调用某个方法时,会自动执行关联着的Handler对象的invoke方法(这个方法是不需要我们显示的调用的),在这个方法里,实现我们要调用的真实角色的方法。
可以看到第二个参数是一个Method对象,这就说明了,当代理实例调用某个方法时会自动的通过反射机制获得该方法的Method对象,并和其他参数一起传入到invoke方法中去。

abstactProxy.sayHello();

通过反编译观看.class文件,我们可以看到 abstactProxy.sayHello() 这一句的内部实现

     */      public final void sayHello()      {          try          {              // 实际上就是调用 DynamicProxy 的public Object invoke(Object proxy, Method method, Object[] args)方法             super.h.invoke(this, m3, null);              return;          }          catch(Error _ex) { }          catch(Throwable throwable)          {              throw new UndeclaredThrowableException(throwable);          }      }     private static Method m1;      private static Method m3;      private static Method m0;      private static Method m2;      // 在静态代码块中获取了4个方法:Object中的equals方法、sayHello中的add方法、Object中的hashCode方法、Object中toString方法      static       {          try          {              m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {                  Class.forName("java.lang.Object")              });              m3 = Class.forName("Proxy.AbstractProxy.AbstractProxy").getMethod("sayHello", new Class[0]);              m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);              m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);          }          catch(NoSuchMethodException nosuchmethodexception)          {              throw new NoSuchMethodError(nosuchmethodexception.getMessage());          }          catch(ClassNotFoundException classnotfoundexception)          {              throw new NoClassDefFoundError(classnotfoundexception.getMessage());          }      }   

// 下面是我们动态代理的一个实现实例

    // 客户端动态代理实现,其实是动态的生成了一个代理对象,由 handler 去访问真正的对象        // 第一步:首先生成一个真正的角色        RealProxy  realProxy = new RealProxy();         // 需要用到反射机制        //  第二步:生成一个代理类的实例,用InvocationHandler来引用        InvocationHandler handler = new DynamicProxy(realProxy);        // 获取 类的信息        Class<?> classType = handler.getClass();        // 第三步:获取真正角色实现的全部接口        Class<?>[]  classInterface = realProxy.getClass().getInterfaces();//      类加载器的说明:  负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用;//      Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,//      它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中        // 运行期间,动态的 生成一个实例对象,java提供了这样一种动态生成类的机制,传进去类加载器ClassLoader        //它并不是真正角色的实例, 也不是DynamicProxy类的实例, 而是这个实例的类型为 $ProxyN, N是一个可以变化的数字          // 生成的这个类实现了 classInterface里面的所有接口,所以可以转换成 classInterface接口的类型,        // 如何实现动态的实例化一个对象呢?        // 首先:根据类加载器和接口构造一个动态代理类        // 其次:通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型InvocationHandler;        // 通过构造函数创建动态代理类实例,构造时调用处理器对象handler作为参数被传入        // 返回一个Object对象,至此动态的生成了一个实例化对象        //  Proxy类的newProxyInstance三个参数分别为: handler的类加载器, 真实角色的所有实现接口,  handler        //  第四步:        AbstractProxy abstactProxy = (AbstractProxy) Proxy.newProxyInstance(classType.getClassLoader(),                 classInterface, handler);        //  每个实例 都会关联一个调用处理器对象,        //  可以通过 Proxy 提供的静态方法 getInvocationHandler 去获得代理类实例的调用处理器对象。        //  在代理类实例上 调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行        // 当动态生成的这个实例,调用了某个方法,程序会立即转到handler中的invoke方法,invoke方法的定义如下        //  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {}        // 第一个参数为要操作的对象, 第二个参数,调用的方法 此时 method=sayHello, 第三个参数为 要调用方法的参数组成的数组        // handler 内部会自动的将sayHello这个方法通过反射转化为Method对象        // 第五步:        abstactProxy.sayHello();

美中不足

诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。

有很多条理由,人们可以否定对 class 代理的必要性,但是同样有一些理由,相信支持 class 动态代理会更美好。接口和类的划分,本就不是很明显,只是到了 Java 中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。

本文参考了:
http://blog.csdn.net/Leonardo9029/article/details/50980525

0 0
原创粉丝点击