zoukankan      html  css  js  c++  java
  • Java动态代理探讨

    代理模式:

      代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。通过代理模式,可以延迟创建对象,限制访问某个对象,也就是说,提供一组方法给普通用户,特别方法给管理员用户。

    UML图:

    简单结构示意图:

      为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。

    按照代理的创建时期,代理类可以分为两种:

    • 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。 
    • 动态代理:在程序运行时,运用反射机制动态创建而成。 

    静态代理:

      为了帮助理解代理模式,来看一下静态代理的示例代码(代码摘自里):

    Count.java 

     1 /** 
     2  * 定义一个账户接口 
     3  * @author Administrator 
     4  */  
     5 public interface Count {  
     6     // 查看账户方法  
     7     public void queryCount();  
     8     // 修改账户方法  
     9     public void updateCount();  
    10 }  

    CountImpl.java 

     1 /** 
     2  * 委托类(包含业务逻辑) 
     3  * @author Administrator 
     4  */  
     5 public class CountImpl implements Count {  
     6   
     7     @Override  
     8     public void queryCount() {  
     9         System.out.println("查看账户方法...");  
    10     }  
    11   
    12     @Override  
    13     public void updateCount() {  
    14         System.out.println("修改账户方法...");  
    15     }  
    16 }

    CountProxy.java

     1 public class CountProxy implements Count {  
     2     private CountImpl countImpl;  
     3     /** 
     4      * 覆盖默认构造器 
     5      * @param countImpl 
     6      */  
     7     public CountProxy(CountImpl countImpl) {  
     8         this.countImpl = countImpl;  
     9     }  
    10   
    11     @Override  
    12     public void queryCount() {  
    13         System.out.println("事务处理之前");  
    14         // 调用委托类的方法;  
    15         countImpl.queryCount();  
    16         System.out.println("事务处理之后");  
    17     }  
    18   
    19     @Override  
    20     public void updateCount() {  
    21         System.out.println("事务处理之前");  
    22         // 调用委托类的方法;  
    23         countImpl.updateCount();  
    24         System.out.println("事务处理之后");  
    25     }  
    26 }  

    TestCount.java 

     1 /** 
     2  *测试Count类 
     3  * @author Administrator 
     4  */  
     5 public class TestCount {  
     6     public static void main(String[] args) {  
     7         CountImpl countImpl = new CountImpl();  
     8         CountProxy countProxy = new CountProxy(countImpl);  
     9         countProxy.updateCount();  
    10         countProxy.queryCount();  
    11     }  
    12 }  

      以上静态代理的代码结合前面的结构图和UML图,相信不难理解代理模式的基本原理。

    JDK动态代理

      为了提高代理的灵活性和可扩展性,减少重复代码,我们可以使用JDK提供的动态代理。

    实现JDK动态代理的步骤:
    1. 通过实现 InvocationHandler 接口创建自己的调用处理器; 
    2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类; 
    3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型; 
    4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    // 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
    InvocationHandler handler = new InvocationHandlerImpl(..);
    
    // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
    Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
    
    // 通过反射从生成的类对象获得构造函数对象
    Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
    
    // 通过构造函数对象创建动态代理类实例
    Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

      实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程:

    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    InvocationHandler handler = new InvocationHandlerImpl(..);
    
    // 通过 Proxy 直接创建动态代理类实例
    Interface proxy = (Interface)Proxy.newProxyInstance( classLoader,
                                                         new Class[] { Interface.class },
                                                         handler );

    JAVA示例代码:

    public class TraceHandler implements InvocationHandler{
       private Object target = null;
       public TraceHandler(Object t) {
          this.target = t;
       }
     
       @Override
       public Object invoke(Object proxy, Method method, Object[] args)
           throws Throwable {
          System.out.print(target);
          System.out.print("." + method.getName() + "(");
          if(args != null) {
             for(int i = 0; i < args.length; ++i) {
                System.out.print(args[i]);
                if(i < args.length-1) System.out.print(", ");
             }
          }
          System.out.println(")");
          return method.invoke(target, args);
       }
    }

    Test.java

    public void test() {
        Object[] elements = new Object[1000];
        for (int i = 0; i < elements.length; i++) {
           Integer val = i+1;
           TraceHandler handler = new TraceHandler(val);
           elements[i] = Proxy.newProxyInstance(null, new Class[]{Comparable.class}, handler);
        }
     
        Integer key = new Random().nextInt(1000) + 1;
        int result = Arrays.binarySearch(elements, key);
        if (result > 0) {
           System.out.println(elements[result]);
        }
     }

       Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。每次生成动态代理类对象时都需要指定一个类装载器对象。

    动态生成的代理类本身的一些特点:

    1. 包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;
    2. 类修饰符:该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;
    3. 类名:格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。
    代理类实例的一特点:       
           在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行,此外,值得注意的是,代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString。
           当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。

    被代理的接口的特点:

    1. 要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。
    2. 这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。
    3. 需被代理的所有非 public 的接口必须在同一个包中,否则代理类生成也会失败。
    4. 接口的数目不能超过 65535,这是 JVM 设定的限制。

    异常处理的特点:

      代理类并不能抛出所有的异常,因为子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。
          如果代理产生了接口方法中不支持的异常,它将会抛出 UndeclaredThrowableException 异常。这个异常是一个 RuntimeException 类型,所以不会引起编译错误。通过该异常的 getCause 方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。

    基于CGLIB的动态代理

      由于JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理,这就要使用cglib动态代理了。

    CGlib概述:

    • cglib(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。
    • cglib封装了asm,可以在运行期动态生成新的class。
    • cglib用于AOP,jdk中的proxy必须基于接口,cglib却没有这个限制。

      JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。 不多说,直接上代码!

    有一个Manager类:

    public class Manager {
    
        public void query() {
            System.out.println("query...");
        }
        
        public void insert() {
            System.out.println("insert...");
        }
        
        public String update() {
            System.out.println("update....");
            return "I'm update";
        }
        
        public void delete() {
            System.out.println("delete....");
        }
    }

    我们想要在这个类中的每个方法前面和后面都打印一句话,这时候我们就可以使用代理了,让我们来看一下这个代理可以怎么写:

    public class AuthProxy implements MethodInterceptor{
        
        @Override
        public Object intercept(Object arg0, Method method, Object[] args,
                MethodProxy proxy) throws Throwable {
            System.out.println("Before...");
            Object result = proxy.invokeSuper(arg0, args);
            System.out.println("After....");
            return result;
        }
    }

      如上,CGLIB实现的代理类必须实现MethodInterceptor接口,该接口中只有一个方法需要实现,即intercept方法。通过MethodProxy中的invokeSuper即可执行被代理类的方法。我们继续往下看。

    public static Manager getInstace(AuthProxy auth) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Manager.class);
            enhancer.setCallback(auth);
            return (Manager) enhancer.create();
        }

      通过以上代码我们就能得到一个Manager的代理类,被AuthProxy代理。

    public void AuthProxyTest() {
            AuthProxy auth = new AuthProxy();
            Manager manager = ManagerFactory.getInstace(auth);
            manager.delete();
            System.out.println();
            manager.query();
            System.out.println();
            String result = manager.update();
            System.out.println("result: " + result);
        }

    下面是一个可以代理不同类的代理生成工厂:

    public class ManagerFactory2 {
        
        public static Manager getInstace(Class clasz, AuthProxy auth) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(clasz);
            enhancer.setCallback(auth);
            return (Manager) enhancer.create();
        }
    }

    对这个工厂进行通用化扩展:

    public class ManagerFactory {
        
        public static Manager getInstace(Class clasz, AuthProxyFilter filter, AuthProxy auth, Object...args) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(clasz);
            
            Callback[] callback = new Callback[args.length+1];
            System.out.println("length: " + callback.length);
            callback[0] = auth;
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Callback) {
                    callback[i+1] = (Callback) args[i];
                }else {
                    callback[i+1] = NoOp.INSTANCE;
                }
            }
            
            enhancer.setCallbacks(callback);
            enhancer.setCallbackFilter(filter);
            return (Manager) enhancer.create();
        }
    AuthProxyFilter.java
    public class AuthProxyFilter implements CallbackFilter{
    
        @Override
        public int accept(Method method) {
            if ("query".equals(method.getName())) {
                return 1;
            }
            return 0;
        }
    }

     =====================华丽的分割线==================================

                                                  源码请猛戳{ 这里

    ===============================================================

    参考资料:

    http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html

    http://www.blogjava.net/stone2083/archive/2008/03/16/186615.html

  • 相关阅读:
    文本字符集转换
    添加HP消息队列
    fedora19/opensuse13.1 配置svn client
    前端html---介绍前端,标签,列表
    数据分析1
    项目流程
    git 使用
    mongo基础
    linux上面pycharm汉化
    pythonNet 09协程
  • 原文地址:https://www.cnblogs.com/big-xuyue/p/4079186.html
Copyright © 2011-2022 走看看