您好,欢迎来到二三娱乐。
搜索
您的当前位置:首页代理-jdk动态代理

代理-jdk动态代理

来源:二三娱乐

摘要

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

  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动态代理只能对接口进行代理,不能代理类。

Copyright © 2019- yule263.com 版权所有 湘ICP备2023023988号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务