代理-jdk动态代理

作者: 理查德成 | 来源:发表于2019-05-19 17:51 被阅读1次

    摘要

    根据代理类生成的时机,代理类在运行时生成,为动态代理;
    本文介绍:

    1. 如何通过JDK实现的动态代理,并详细介绍使用方式(Proxy, InvocationHandler);
    2. 实现原理ProxyGenerator。

    一、代理模式回顾

    代理模式

    二、基于JDK的动态代理

    首先,已有主题接口Subject,以及真正主题对象类 RealSubject:

    Subject

    /**
     * Subject主题接口
     */
    public interface Subject {
        void doTask();
    }
    

    RealSubject:

    /**
     * 具体类,真正具有接口实现逻辑的类
     */
    public class RealSubject implements Subject {
        @Override
        public void doTask() {
            try {
                Thread.sleep(5000L);
                System.out.println("task completed.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    这就像在实际编码中,已经有了一个基于面向接口编程,优良设计的组件,组件实际提供功能的是类RealSubject。
    现在要求在计算组件核心业务的耗时,我们不能侵入地修改核心业务提供类RealSubject的源码,代理模式就发挥作用了。

    静态代理已经介绍过,这里使用基于JDK的动态代理来完成这个需求。

    1) 首先创建调用处理器

    public class SubjectHandler<T extends Subject> implements InvocationHandler {
        private T subject;
        public SubjectHandler(T subject) {
            this.subject = subject;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 在代理真实对象前我们可以添加一些自己的操作
            long start = System.currentTimeMillis();
            System.out.println("task begins");
            // 调用真实主题对象方法
            Object invoke = method.invoke(subject, args);
            // 在代理真实对象后我们也可以添加一些自己的操作
            System.out.println("task ends, duration: \t" + (-start + System.currentTimeMillis()) / 1000 + "s");
            return invoke;
        }
    }
    

    2) 创建动态代理

    public static void main(String[] args) {
            // 我们要代理的真实对象
            Subject realSubject = new RealSubject();
            // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
            InvocationHandler handler = new SubjectHandler<>(realSubject);
            /*
             * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
             * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
             * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
             * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
             */
            Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
                    realSubject.getClass().getInterfaces(), handler);
            // 调用代理类方法
            subject.doTask();
        }
    

    三、JDK的动态代理详细

    明面上的jdk动态代理功能主要由两个类提供:

    1. InvocationHandler
    2. Proxy

    1)InvocationHandler

    jdk对java.lang.reflect.InvocationHandler的说明:

    每一个动态代理实例都关联一个调用处理器(InvocationHandler),当调用代理实例的方法时,实际调用被封装,转发给给其关联的调用处理器的invoke方法;

    在实现上,调用处理器的invoke方法是通过反射的方式调用真实主题对象的方法,因此调用处理器必须持有真实主题对象的引用,这样,动态代理类间接将请求委托给真实主题对象。从而符合代理模式,真实请求通过代理类委托给真实主题对象处理的逻辑。

    invoke(Object proxy, Method method, Object[] args)参数说明:

    1. proxy是动态代理实例;
    2. method是Method实例,对应动态代理类实现的接口的方法;
    3. args是调用参数。

    2)Proxy

    java.lang.reflect.Proxy提供创建动态代理类以及动态代理类实例的静态方法,同时也是其创建出来的所有动态代理类的父类。

    动态代理类实例,有两种创建方式:

    1. 直接调用其newProxyInstance(ClassLoader, interfaces, invocationHandler)方法,获得代理类实例;

    2. 调用其getProxyClass(ClassLoader, interface)方法获取动态代理类对象,然后获取动态代理类的带参构造构造器,最后通过反射构造动态代理类实例:

       Class<?> proxyClass = Proxy.getProxyClass(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces());
       Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
       Subject subject  = (Subject) constructor.newInstance(handler);
      

    其中:

    1. ClassLoader是加载动态代理类所需的类加载器;
    2. interfaces是动态代理类要实现的接口列表;
    3. invocationHandler是动态代理类关联的调用处理器。

    动态代理类是在运行时实现指定接口列表的类,每一个动态代理类都关联一个调用处理器,通过调用处理器,间接将请求委托给真实主题对象处理。

    3)动态代理类

    在调用处理器的invoke方法中,第一个参数Object proxy是动态代理类实例,通过这个对象,可以一窥动态代理类。

    Class<?> proxyClass = proxy.getClass();
    System.out.println(proxyClass);
    System.out.println(proxyClass.getSuperclass());
    System.out.println(Arrays.toString(proxyClass.getInterfaces()));
    

    输出:

    class com.sun.proxy.$Proxy0
    class java.lang.reflect.Proxy
    [interface design.pattern.proxy.Subject]
    

    四、JDK的动态代理原理

    jdk动态代理的核心,就在于动态代理类的创建;

    抽丝剥茧,发现在Proxy类的内部类 ProxyClassFactory中的apply方法,使用:

    byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags);

    创建动态代理类的字节码。

    那么,可以使用ProxyGenerator手动创建动态代理类的字节码,然后通过字节码反编译,探究动态代理类:

    public static void main(String[] args) {
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",
                new Class[]{Subject.class}, Modifier.PUBLIC);
        String path = DynamicClassGenTest.class.getResource("").getPath() + "$Proxy0.class";
        File file = new File(path);
        try (FileOutputStream fos = new FileOutputStream(file)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("代理类class文件写入成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

    反编译字节码文件$Proxy0.class伪代码:

    public class $Proxy0 extends Proxy implements Subject {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
        // equals, toString, hashCode实现
        public final void doTask() throws  {
            try {
                super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
               // toString method
                m3 = Class.forName("design.pattern.proxy.Subject").getMethod("doTask");
                // hashCode method
            } 
            // catch block
        }
    }
    
    1. 首先,动态代理类实现要代理的接口Subject,继承类Proxy。
    2. 在static代码块中,使用反射,接口Subject的doTask()方法,并使用反射Method引用之;
    3. Subject#doTask()方法的实现,将this(反映到调用处理器中就是实际动态代理对象),反射类Methods实例method(Subject的doTask()方法),调用参数作为参数,调用父类Proxy的InvocationHandler的invoke方法。

    缺陷

    根据动态代理类的创建原理,不难看出,动态代理类必须实现要代理的接口;因此jdk动态代理只能对接口进行代理,不能代理类。

    另外,jdk动态代理基于JAVA反射,这决定了其性能不会太高。

    总结

    使用JDK动态代理时,通过类Proxy创建动态代理类,每一个动态代理类关联一个调用处理器,调用处理器将请求委托给真正具备处理能力的实际对象。

    根据jdk动态代理类的原理,jdk动态代理只能对接口进行代理,不能代理类。

    相关文章

      网友评论

        本文标题:代理-jdk动态代理

        本文链接:https://www.haomeiwen.com/subject/wtmqzqtx.html