zoukankan      html  css  js  c++  java
  • JAVA设计模式-代理模式

    代理模式是Java常见的设计模式之一。所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。
    为什么要采用这种间接的形式来调用对象呢?一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。
    在现实生活中,这种情形非常的常见,比如请一个律师代理来打官司。

    代理模式的UML图

    从UML图中,可以看出代理类与真正实现的类都是继承了抽象的主题类,这样的好处在于代理类可以与实际的类有相同的方法,可以保证客户端使用的透明性。

    代理模式的实现

    代理模式可以有两种实现的方式,一种是静态代理类,另一种是各大框架都喜欢的动态代理。动态代理跟静态代理一个最大的差别就是:动态代理是在执行时刻动态的创建出代理类及其对象。下面我们主要讲解一下这两种代理模式

    静态代理

    我们先看针对上面UML实现的例子,再看静态代理的特点。
    Subject接口的实现

    public interface Subject {
        void visit();
    }

    实现了Subject接口的两个类:

    public class RealSubject implements Subject {
    
        private String name = "byhieg";
        @Override
        public void visit() {
            System.out.println(name);
        }
    }
    public class ProxySubject implements Subject{
    
        private Subject subject;
    
        public ProxySubject(Subject subject) {
            this.subject = subject;
        }
    
        @Override
        public void visit() {
            subject.visit();
        }
    }

    具体的调用如下:

    public class Client {
    
        public static void main(String[] args) {
            ProxySubject subject = new ProxySubject(new RealSubject());
            subject.visit();
        }
    }

    通过上面的代理代码,我们可以看出代理模式的特点,代理类接受一个Subject接口的对象,任何实现该接口的对象,都可以通过代理类进行代理,增加了通用性。但是也有缺点,每一个代理类都必须实现一遍委托类(也就是realsubject)的接口,如果接口增加方法,则代理类也必须跟着修改。其次,代理类每一个接口对象对应一个委托对象,如果委托对象非常多,则静态代理类就非常臃肿,难以胜任。

    动态代理

    动态代理有别于静态代理,是根据代理的对象,动态创建代理类。这样,就可以避免静态代理中代理类接口过多的问题。动态代理是实现方式,是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy,通过固定的规则生成。
    其步骤如下:

    1. 编写一个委托类的接口,即静态代理的(Subject接口)
    2. 实现一个真正的委托类,即静态代理的(RealSubject类)
    3. 创建一个动态代理类,实现InvocationHandler接口,并重写该invoke方法
    4. 在测试类中,生成动态代理的对象。

    第一二步骤,和静态代理一样,不过说了。第三步,代码如下:

    public class DynamicProxy implements InvocationHandler {
        private Object object;
        public DynamicProxy(Object object) {
            this.object = object;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = method.invoke(object, args);
            return result;
        }
    }

    第四步,创建动态代理的对象

    Subject realSubject = new RealSubject();
    DynamicProxy proxy = new DynamicProxy(realSubject);
    ClassLoader classLoader = realSubject.getClass().getClassLoader();
    Subject subject = (Subject) Proxy.newProxyInstance(classLoader, new  Class[]{Subject.class}, proxy);
    subject.visit();

    创建动态代理的对象,需要借助Proxy.newProxyInstance。该方法的三个参数分别是:

    • ClassLoader loader表示当前使用到的appClassloader。
    • Class<?>[] interfaces表示目标对象实现的一组接口。
    • InvocationHandler h表示当前的InvocationHandler实现实例对象。

    静态代理是在编译的时候就确定了代理类详细类型。假设有多个类须要代理。那么就得创建多个。

    另一点,假设Subject中新增了一个方法,那么相应的实现接口的类中也要相应的实现这些方法。

    动态代理的做法:在执行时刻。能够动态创建出一个实现了多个接口的代理类。每一个代理类的对象都会关联一个表示内部处理逻辑的InvocationHandler接 口的实现。当使用者调用了代理对象所代理的接口中的方法的时候。这个调用的信息会被传递给InvocationHandler的invoke方法。在 invoke方法的參数中能够获取到代理对象、方法相应的Method对象和调用的实际參数。invoke方法的返回值被返回给使用者。这样的做法实际上相 当于对方法调用进行了拦截。
    类图例如以下所看到的:

    proxy2.png

    上面类图中使用的JDK中的Proxy类。所以是须要要办法来告诉Proxy类须要做什么,不能像静态代理一样。将代码放到Proxy类中,由于如今Proxy不是直接实现的。既然这种代码不能放在Proxy类中,那么就须要一个InvocationHandler,InvocationHandler的工作就是响应代理的不论什么调用。

    动态代理实现过程

    详细有例如以下四步骤:
    • 通过实现 InvocationHandler 接口创建自己的调用处理器;
    • 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
    • 通过反射机制获得动态代理类的构造函数,其唯一參数类型是调用处理器接口类型。
    • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为參数被传入。

      一个详细的样例

      接着上面的类图和静态代理中的样例,我们分别创建Subject和RealSubject
    • Subject
      package ProxyMode;
      
      /*
       * 抽象接口。相应类图中的Subject
       * 
       */
      
      public interface Subject {
      
          public void SujectShow();
      
      }
      
      
      
    • RealSubject
      package ProxyMode;
      
      
      public class RealSubject implements Subject{
      
          @Override
          public void SujectShow() {
              // TODO Auto-generated method stub
              System.out.println("杀人是我指使的,我是幕后黑手!

      By---"+getClass()); } }

    • 建立InvocationHandler用来响应代理的不论什么调用
      package ProxyMode;
      
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      
      public class ProxyHandler implements InvocationHandler {
      
          private Object proxied;   
      
            public ProxyHandler( Object proxied )   
            {   
              this.proxied = proxied;   
            }   
      
      
          @Override
          public Object invoke(Object proxy, Method method, Object[] args)
                  throws Throwable {
      
              System.out.println("准备工作之前:");
      
              //转调详细目标对象的方法
                Object object=   method.invoke( proxied, args);
      
               System.out.println("工作已经做完了!");
               return object;
          }
      
      }
      
      
    • 动态代理类測试,这个代理类中再也不用实现Subject接口。能够动态的获得RealSubject接口中的方法
      package ProxyMode;
      
      
      import java.lang.reflect.Proxy;
      
      public class DynamicProxy  {
      
          public static void main( String args[] )   
            {   
              RealSubject real = new RealSubject();   
              Subject proxySubject = (Subject)Proxy.newProxyInstance(Subject.class.getClassLoader(), 
               new Class[]{Subject.class}, 
               new ProxyHandler(real));
      
              proxySubject.SujectShow();;
      
            }   
      }
      

      測试结果

      准备工作之前:
      杀人是我指使的,我是幕后黑手!

      By---class ProxyMode.RealSubject 工作已经做完了!

      Proxy和InvocationHandler重要部分源代码分析

      java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

      清单 1. Proxy 的静态方法
      // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器,比方上面代码中的ProxyHandler
      static InvocationHandler getInvocationHandler(Object proxy) 
      
      // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
      static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
      
      // 方法 3:该方法用于推断指定类对象是否是一个动态代理类
      static boolean isProxyClass(Class cl) 
      
      // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
      static Object newProxyInstance(ClassLoader loader, Class[] interfaces, 
          InvocationHandler h)
      
      以下重点看看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()); } }

      看这种方法的三个參数
      public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
      
    • loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行载入
    • interfaces: 一个Interface对象的数组。表示的是我将要给我须要代理的对象提供一组什么接口。假设我提供了一组接口给它。那么这个代理对象就宣称实现了该接口(多态)。这样我就能调用这组接口中的方法了
    • h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
      从上面JDK源代码中能够看出getProxyClass方法才是newProxyInstance方法中最重要的,该方法负责为一组接口动态地生成代理类类型对象。以下開始解析proxy中的getProxyClass方法
      该方法总共能够分为四个步骤:
    • 对这组接口进行一定程度的安全检查,包含检查接口类对象是否对类装载器可见而且与类装载器所能识别的接口类对象是全然同样的,还会检查确保是 interface 类型而不是 class 类型。

      这个步骤通过一个循环来完毕,检查通过后将会得到一个包括全部接口名称的字符串数组,记为 String[] interfaceNames

       for (int i = 0; i < interfaces.length; i++) {
      
                  // 验证类载入程 序 解 析 该接口到同一类对象的名称。

      String interfaceName = interfaces[i].getName(); Class<?> interfaceClass = null; try { interfaceClass = Class.forName(interfaceName, false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != interfaces[i]) { throw new IllegalArgumentException( interfaces[i] + " is not visible from class loader"); } // 验证类对象真正代表一个接口 if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } //验证这个接口是不是反复的 if (interfaceSet.contains(interfaceClass)) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } interfaceSet.add(interfaceClass); //interfaceset是一个hashset集合 interfaceNames[i] = interfaceName; }

    • 从 loaderToCache 映射表中获取以类装载器对象为keyword所相应的缓存表。假设不存在就创建一个新的缓存表并更新到 loaderToCache。

      缓存表是一个 HashMap 实例,正常情况下它将存放键值对(接口名字列表。动态生成的代理类的类对象引用)。当代理类正在被创建时它会暂时保存(接口名字列表,pendingGenerationMarker)。标记 pendingGenerationMarke 的作用是通知兴许的同类请求(接口数组同样且组内接口排列顺序也同样)代理类正在被创建。请保持等待直至创建完毕。

       synchronized (cache) {
      do { 
          // 以接口名字列表作为关键字获得相应 cache 值
          Object value = cache.get(key); 
          if (value instanceof Reference) { 
              proxyClass = (Class) ((Reference) value).get(); 
          } 
          if (proxyClass != null) { 
              // 假设已经创建,直接返回,这里很重要。假设已经创建过代理类,那么不再创建
              return proxyClass; 
          } else if (value == pendingGenerationMarker) { 
              // 代理类正在被创建,保持等待
              try { 
                  cache.wait(); 
              } catch (InterruptedException e) { 
              } 
              // 等待被唤醒,继续循环并通过二次检查以确保创建完毕,否则又一次等待
              continue; 
          } else { 
              // 标记代理类正在被创建
              cache.put(key, pendingGenerationMarker); 
              // break 跳出循环已进入创建过程
              break; 
      } while (true);
      }
      
    • 动态创建代理类的类对象。

      首先是确定代理类所在的包,其原则如前所述。假设都为 public 接口,则包名为空字符串表示顶层包。假设全部非 public 接口都在同一个包。则包名与这些接口的包名相同;假设有多个非 public 接口且不同包,则抛异常终止代理类的生成。

      确定了包后。就開始生成代理类的类名,相同如前所述按格式“$ProxyN”生成。

      // 动态地生成代 理类的字节码数组
      byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); 
      try { 
          // 动态地定义新生成的代理类
          proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, 
              proxyClassFile.length); 
      } catch (ClassFormatError e) { 
          throw new IllegalArgumentException(e.toString()); 
      } 
      
      // 把生成的代理类的类对象记录进 proxyClasses 表
      proxyClasses.put(proxyClass, null);
      
      
      到了这里,事实上generateProxyClass方法也是一个重点,可是generateProxyClass的方法代码跟踪不了。位于并未公开的 sun.misc 包,有若干常量、变量和方法以完毕这个奇妙的代码生成的过程,可是 sun 并没有提供源码以供研读
    • 结尾部分
      依据结果更新缓存表,假设成功则将代理类的类对象引用更新进缓存表。否则清楚缓存表中相应关键值,最后唤醒全部可能的正在等待的线程。
      
       synchronized (cache) {
                      if (proxyClass != null) {
                          cache.put(key, new WeakReference<Class<?>>(proxyClass));
                      } else {
                          cache.remove(key);
                      }
                      cache.notifyAll();
                  }
      

      java.lang.reflect.InvocationHandler:这是调用处理器接口,它自己定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对托付类的代理訪问。

      InvocationHandler 的核心方法。我们最关心的是Invoke方法为什么会被调用。见以下分析:
      // 该方法负责集中处理动态代理类上的所 有方法调用。

      //第一个參数既是代理类实例, //第二个參数是被调用的方法对象 // 第三个方法是调用參数。调用处理器依据这三个參数进行预处理或分派到托付类实例上发射运行 Object invoke(Object proxy, Method method, Object[] args)

      每次生成动态代理类对象时都须要指定一个实现了该接口的调用处理器对象(參见 newProxyInstance 的第三个參数)。
      非常多人肯定跟我一样,我们在Handler中调用的method.invoke方法中并没有显示的调用invoke方法,仅仅是在newProxyInstance中应用了一个handler对象。有了上面关于newProxyInstance的源代码分析。我们知道了 newproxyinstance生成了一个$Proxy0类代理。当调用Subjectshow()方法时,事实上调用的$Proxy0的SubjectShow()方法,从而调用父类Proxy中传进来第三个參数(h)的的Invoke方法。
      //这种方法是 Proxy源代码中的
        protected Proxy(InvocationHandler h) {
              this.h = h;
          }
      
      

      来看NewProxyInstance方法生成的$Proxy0代理类的源代码

      public final class $Proxy0 extends Proxy implements Subject {
          private static Method m1;
          private static Method m0;
          private static Method m3;
          private static Method m2;
      
          static {
              try {
                  m1 = Class.forName("java.lang.Object").getMethod("equals",
                          new Class[] { Class.forName("java.lang.Object") });
      
                  m0 = Class.forName("java.lang.Object").getMethod("hashCode",
                          new Class[0]);
      
                  m3 = Class.forName("***.RealSubject").getMethod("request",
                          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());
              }
          } //static
      
          public $Proxy0(InvocationHandler invocationhandler) {
              super(invocationhandler);
          }
      
          @Override
          public final boolean equals(Object obj) {
              try {
                  return ((Boolean) super.h.invoke(this, m1, new Object[] { obj })) .booleanValue();
              } catch (Throwable throwable) {
                  throw new UndeclaredThrowableException(throwable);
              }
          }
      
          @Override
          public final int hashCode() {
              try {
                  return ((Integer) super.h.invoke(this, m0, null)).intValue();
              } catch (Throwable throwable) {
                  throw new UndeclaredThrowableException(throwable);
              }
          }
      
          public final void SubjectShow() {
              try {
                  super.h.invoke(this, m3, null); //就是这个地方  调用h.invoke()
                  return;
              } catch (Error e) {
              } catch (Throwable throwable) {
                  throw new UndeclaredThrowableException(throwable);
              }
          }
      
          @Override
          public final String toString() {
              try {
                  return (String) super.h.invoke(this, m2, null);
              } catch (Throwable throwable) {
                  throw new UndeclaredThrowableException(throwable);
              }
          }
      }
      
      
      从上面的$Proxy0中找到方法SubjectSHow()方法,我们能够看到中间调用了父类Proxy的參数Handler h的invoke方法,也就调用了ProxyHandler中的invoke()方法。还能够看到¥Proxy0还代理了equals()、hashcode()、tostring()这三个方法,至此动态代理实现机制就非常清楚了
       
      參考文章:http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/
  • 相关阅读:
    如何完全删除Linux应用
    IP地址获取工具类
    日期处理工具类
    Cookies的工具类
    权限管理系统学习笔记
    SpringBoot中JPA的一些基本操作
    Mysql和Java的数据类型对应表
    MySQL中的tinyint
    幂等性浅谈
    链接
  • 原文地址:https://www.cnblogs.com/yyxq/p/9714392.html
Copyright © 2011-2022 走看看