zoukankan      html  css  js  c++  java
  • JDK的动态代理机制

    jdk的动态代理是基于接口的,必须实现了某一个或多个任意接口才可以被代理,并且只有这些接口中的方法会被代理。看了一下jdk带的动态代理api,发现没有例子实在是很容易走弯路,所以这里写一个加法器的简单示例。

    1
    2
    3
    4
    5
    6
    7
    // Adder.java
      
    package test;
      
    public interface Adder {
        int add(int a, int b);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // AdderImpl.java
      
    package test;
      
    public class AdderImpl implements Adder {
        @Override
        public int add(int a, int b) {
            return a + b;
        }
    }
    1
    现在我们有一个接口Adder以及一个实现了这个接口的类AdderImpl,写一个Test测试一下。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // Test.java
      
    package test;
      
    public class Test {
        public static void main(String[] args) throws Exception {
            Adder calc = new AdderImpl();
            int result = calc.add(1, 2);
            System.out.println("The result is " + result);
        }
    }

    很显然,控制台会输出:

    1
    The result is 3

    然而现在我们需要在加法器使用之后记录一些信息以便测试,但AdderImpl的源代码不能更改,就像这样:

    1
    2
    Proxy: invoke add() at 2009-12-16 17:18:06
    The result is 3

    动态代理可以很轻易地解决这个问题。我们只需要写一个自定义的调用处理器(实现接口java.lang.reflect.InvokationHandler),然后使用类java.lang.reflect.Proxy中的静态方法生成Adder的代理类,并把这个代理类当做原先的Adder使用就可以。

    第一步:实现InvokationHandler,定义调用方法时应该执行的动作。

    自定义一个类MyHandler实现接口java.lang.reflect.InvokationHandler,需要重写的方法只有一个:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // AdderHandler.java
      
    package test;
      
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
      
    class AdderHandler implements InvocationHandler {
        /**
         * @param proxy 接下来Proxy要为你生成的代理类的实例,注意,并不是我们new出来的AdderImpl
         * @param method 调用的方法的Method实例。如果调用了add(),那么就是add()的Method实例
         * @param args 调用方法时传入的参数。如果调用了add(),那么就是传入add()的参数
         * @return 使用代理后将作为调用方法后的返回值。如果调用了add(),那么就是调用add()后的返回值
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            // ...
        }
    }

    使用代理后,这个方法将取代指定的所有接口中的所有方法的执行。在本例中,调用adder.add()方法时,实际执行的将是invoke()。所以为了有正确的结果,我们需要在invoke()方法中手动调用add()方法。再看看invoke()方法的参数,正好符合反射需要的所有条件,所以这时我们马上会想到这样做:

    1
    Object returnValue = method.invoke(proxy, args);

    如果你真的这么做了,那么恭喜你,你掉入了jdk为你精心准备的圈套。proxy是jdk为你生成的代理类的实例,实际上就是使用代理之后adder引用所指向的对象。由于我们调用了adder.add(1, 2),才使得invoke()执行,如果在invoke()中使用method.invoke(proxy, args),那么又会使invoke()执行。没错,这是个死循环。然而,invoke()方法没有别的参数让我们使用了。最简单的解决方法就是,为MyHandler加入一个属性指向实际被代理的对象。所以,因为jdk的冷幽默,我们需要在自定义的Handler中加入以下这么一段:

    1
    2
    3
    4
    5
    6
    // 被代理的对象
    private Object target;
     
    public AdderHandler(Object target) {
        this.target = target;
    }

    喜欢的话还可以加上getter/setter。接着,invoke()就可以这么用了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // AdderHandler.java
      
    package test;
      
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.util.Date;
      
    class AdderHandler implements InvocationHandler {
        // 被代理的对象
        private Object target;
      
        public AdderHandler(Object target) {
            this.target = target;
        }
          
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            // 调用被代理对象的方法并得到返回值
            Object returnValue = method.invoke(target, args);
            // 调用方法前后都可以加入一些其他的逻辑
            System.out.println("Proxy: invoke " + method.getName() + "() at " + new Date().toLocaleString());
            // 可以返回任何想要返回的值
            return returnValue;
        }
    }

    第二步:使用jdk提供的java.lang.reflect.Proxy生成代理对象。

    使用newProxyInstance()方法就可以生成一个代理对象。把这个方法的签名拿出来:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
     * @param loader 类加载器,用于加载生成的代理类。
     * @param interfaces 需要代理的接口。这些接口的所有方法都会被代理。
     * @param h 第一步中我们建立的Handler类的实例。
     * @return 代理对象,实现了所有要代理的接口。
     */
    public static Object newProxyInstance(ClassLoader loader,
              Class<?>[] interfaces,
              InvocationHandler h)
    throws IllegalArgumentException

    这个方法会做这样一件事情,他将把你要代理的全部接口用一个由代码动态生成的类类实现,所有的接口中的方法都重写为调用InvocationHandler.invoke()方法。这个类的代码类似于这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 模拟Proxy生成的代理类,这个类是动态生成的,并没有对应的.java文件。
      
    class AdderProxy extends Proxy implements Adder {
        protected AdderProxy(InvocationHandler h) {
            super(h);
        }
      
        @Override
        public int add(int a, int b) {
            try {
                Method m = Adder.class.getMethod("add", new Class[] {int.class, int.class});
                Object[] args = {a, b};
                return (Integer) h.invoke(this, m, args);
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        }
    }

    据api说,所有生成的代理类都是Proxy的子类。当然,生成的这个类的代码你是看不到的,而且Proxy里面也是调用sun.XXX包的api生成;一般情况下应该是直接生成了字节码。然后,使用你提供的ClassLoader将这个类加载并实例化一个对象作为代理返回。

    看明白这个方法后,我们来改造一下main()方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    // Test.java
      
    package test;
      
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
      
    public class Test {
        public static void main(String[] args) throws Exception {
            Adder calc = new AdderImpl();
              
            // 类加载器
            ClassLoader loader = Test.class.getClassLoader();
            // 需要代理的接口
            Class[] interfaces = {Adder.class};
            // 方法调用处理器,保存实际的AdderImpl的引用
            InvocationHandler h = new AdderHandler(calc);
            // 为calc加上代理
            calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h);
              
            /* 什么?你说还有别的需求? */
            // 另一个处理器,保存前处理器的引用
            // InvocationHandler h2 = new XXOOHandler(h);
            // 再加代理
            // calc = (Adder) Proxy.newProxyInstance(loader, interfaces, h2);
              
            int result = calc.add(1, 2);
            System.out.println("The result is " + result);
        }
    }

    输出结果会是什么呢?

    1
    2
    Proxy: invoke add() at 2009-12-16 18:21:33
    The result is 3

    对比一下之前的结果,你会发现这点东西写了我一个多小时。再来看看JDK有多懒:

    target完全可以在代理类中生成。
    实际方法都需要手动调用,可见代理类中重写所有的方法都只有一句话:return xxx.invoke(ooo);

    不过这么写也有他的理由,target自己管理,方法你爱调不调 ﹃_﹃;如果他调了,InvocationHandler接口中恐怕就需要两个方法了,还要判断返回、处理参数等等。

  • 相关阅读:
    JDBC 复习4 批量执行SQL
    JDBC 复习3 存取Oracle大数据 clob blob
    Oracle复习
    Linux命令(1)grep
    JDBC 复习2 存取mysql 大数据
    JDBC 复习1 DBUtil
    php 环境搭建问题
    Windows 批处理 bat 开启 WiFi 菜单选项 设置ID PWD
    Bat 批处理启动和停止Oracle 服务
    docker 学习1 WSL docker ,Windows docker
  • 原文地址:https://www.cnblogs.com/feifeihu/p/2644701.html
Copyright © 2011-2022 走看看