分析JDK动态代理的实现

简介: 分析JDK动态代理的实现

前言

在java中,动态代理分为两类:

  • 基于接口的JDK动态代理
  • 基于类的CGlib动态代理

而今天我要讲的是前者,基于接口的动态代理。动态代理在框架中的应用非常广泛,理解了动态代理,对spring,mybatis等常用框架的源码阅读也非常有帮助。这篇文章的由来也是因为肺炎(各位一定不用乱跑阿,出门也要记得带口罩,这个真的很重要!!!),只能在家看mybatis源码,看到sqlSession.getMapper(Class class)方法时,因为源码中用到了JDK动态代理,所以决定深挖一下JDK动态代理的实现。

看完这篇文章,你能收获的知识:

  • 为什么JDK动态代理只能基于接口?
  • 代理类是如何生成并且创建的?
  • 如何反编译代理类?
  • 为什么会自动执行代理对象的invoke()方法?
  • 代理对象的invoke(Object proxy, Method method, Object[] args)中的method参数是如何知道我们执行的是哪个方法。

使用

后面的所有解析都会基于这个例子进行讲解

因为JDK动态代理是基于接口的,所有我们首先要创建一个接口

public interface Hello {
    void sayHello();
}

然后我们给这个接口一个实现类,这个实现类并不是必须的,mybatis中就是直接利用接口,而没有这个实现类,这里是为了更加易于理解。

public class Amy implements Hello{
    private String name;
    public Amy(String name) {
        this.name = name;
    }
    public void sayHello() {
        System.out.println(name + " say hello");
    }
}

接着我们创建一个代理类(这个代理类跟下文源码分析中JDK生成的代理类并不是同一回事,请务必分清楚,以免理解出错),JDK动态代理规定代理类必须实现InvocationHandler接口


public class ProxyHello implements InvocationHandler {
    private Hello hello;
    public ProxyHello(Hello hello) {
        this.hello = hello;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before say hello");
        method.invoke(hello, args);
        System.out.println("after say hello");
        return null;
    }
}

最后我们写一个测试类,创建一个代理对象,执行sayHello()方法,看看输出的结果

public class Test {
    public static void main(String[] args) {
        Amy amy = new Amy("Amy");
        ProxyHello proxyHello = new ProxyHello(amy);
        Hello helloProxy = (Hello) Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                new Class[]{Hello.class},
                proxyHello);
        helloProxy.sayHello();
    }
}

最后输出的结果

image.png

通过输出结果可以知道,实际上执行的是代理对象里面的invoke()方法。

源码分析

通过上面的例子可以知道,创建代理对象是通过Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)进行创建的,接收三个参数

  • 类加载器,我们直接使用当前线程的类加载器,一般是AppClassLoader
  • 要代理的接口,例子中就是Hello.class,也就是我们创建的接口
  • 代理对象,例子中是ProxyHello类的对象

newProxyInstance()

没有写注释的语句说明不影响理解源码,都是一些是否有操作权限的校验,所以可以暂时忽略。

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        /*
         * 这句是重点,也是创建代理类的方法,注意这里只是创建了类,
         * 也就是我们平时写代码创建的.class文件,并不是代理对象
         * 主要也是分析这个方法!!!
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            // 获取创建的代理类的构造函数,之后我们会反编译看一下代理类的实现
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            // 通过代理类的构造函数创建一个代理类的实例对象,并且返回这个代理对象
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

getProxyClass0()


private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    // 接口数量不能大于65535个,一般都用不了那么多
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    // 从缓冲中获取代理类,如果缓存中不存在再通过ProxyClassFactory创建
    // 缓存是通过弱引用来缓存,弱引用很适合用来做缓存
    return proxyClassCache.get(loader, interfaces);
}

proxyClassCache.get()

public V get(K key, P parameter) {
    Objects.requireNonNull(parameter);
    expungeStaleEntries();
    // 创建一个以classloader为引用的缓存键
    Object cacheKey = CacheKey.valueOf(key, refQueue);
    // map中有二级缓存
    // 第一级缓存:通过上面创建的缓存键获取对应的通过该类加载器加载的
    // 所有代理类,也就是第二级缓存的Map
    // 第二级缓存:以接口创建的虚引用为键,对应接口的代理类的供应商为值
    // 这条语句就是获取第二级缓存
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
    if (valuesMap == null) {
        ConcurrentMap<Object, Supplier<V>> oldValuesMap
            = map.putIfAbsent(cacheKey,
                              valuesMap = new ConcurrentHashMap<>());
        if (oldValuesMap != null) {
            valuesMap = oldValuesMap;
        }
    }
    // 根据接口创建第二级缓存的键
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    // 从第二级缓存中获取创建代理类的供应商
    Supplier<V> supplier = valuesMap.get(subKey);
    // Factory是实现了Supplier接口的工厂类
    Factory factory = null;
    while (true) {
        if (supplier != null) {
            // 如果供应商存在,那么直接从供应商获取代理类
            V value = supplier.get();
            if (value != null) {
                // 获取到代理类,直接返回
                return value;
            }
        }
        // 创建一个新的供应商
        if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }
        // 如果供应商不存在,那么则将上面创建的供应商存入缓存中,
        // 然后重新开始循环
        if (supplier == null) {
            supplier = valuesMap.putIfAbsent(subKey, factory);
            if (supplier == null) {
                supplier = factory;
            }
        } else {
            // 如果存在供应商,但是没有获取到代理类
            // 那么则将新创建的供应商替换旧的供应商
            if (valuesMap.replace(subKey, supplier, factory)) {
                // 替换成功,重新循环
                supplier = factory;
            } else {
                // 替换失败,重新使用旧的供应商继续尝试获取代理类
                supplier = valuesMap.get(subKey);
            }
        }
    }
}

我们真正创建和获取代理类的方法就是supplier.get()

通常情况下,第一次循环都是获取不到代理类的,第一次循环都是创建一个供应商,然后存入到缓存中,第二次循环从缓存中获取到供应商,然后从供应商处获取代理类。

supplier.get()

因为supplier变量是引用了Factory类的对象,所以我们实际要看的是Factory类的get()方法

Factory类是WeakCache的私有内部类

private final class Factory implements Supplier<V> {
    private final K key;
    private final P parameter;
    private final Object subKey;
    private final ConcurrentMap<Object, Supplier<V>> valuesMap;
    Factory(K key, P parameter, Object subKey,
            ConcurrentMap<Object, Supplier<V>> valuesMap) {
        this.key = key;
        this.parameter = parameter;
        this.subKey = subKey;
        this.valuesMap = valuesMap;
    }
    @Override
    public synchronized V get() { // serialize access
        // 重新检查是否存在对应的代理类供应商
        Supplier<V> supplier = valuesMap.get(subKey);
        if (supplier != this) {
            return null;
        }
        // else still us (supplier == this)
        // create new value
        V value = null;
        try {
            // 这句是重点,valueFactory是创建代理类的工厂
            // 由该工厂创建一个真正的代理类
            // 它是ProxyClassFactory类的实例对象
            value = Objects.requireNonNull(valueFactory.apply(key, parameter));
        } finally {
            if (value == null) {
                valuesMap.remove(subKey, this);
            }
        }
        assert value != null;
        // 为代理类创建一个虚引用
        CacheValue<V> cacheValue = new CacheValue<>(value);
        reverseMap.put(cacheValue, Boolean.TRUE);
        // 尝试将新创建的代理类的缓存供应商替换旧的供应商
        // 也就是上一个方法创建Factory类的对象
        // 该方法执行必须成功
        if (!valuesMap.replace(subKey, this, cacheValue)) {
            throw new AssertionError("Should not reach here");
        }
        // 返回代理类
        return value;
    }
}

valueFactory.apply()

创建代理类的方法

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    // prefix for all proxy class names
    private static final String proxyClassNamePrefix = "$Proxy";
    // next number to use for generation of unique proxy class names
    private static final AtomicLong nextUniqueNumber = new AtomicLong();
    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) {
            /*
             * 验证类加载器是否将此接口的名称解析为同一个Class对象
             */
            Class<?> interfaceClass = null;
            try {
                interfaceClass = Class.forName(intf.getName(), false, loader);
            } catch (ClassNotFoundException e) {
            }
            if (interfaceClass != intf) {
                throw new IllegalArgumentException(
                    intf + " is not visible from class loader");
            }
            // 验证Class对象是否是接口
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }
            /*
             *验证接口没有重复
             */
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
        }
        // 代理类的包名
        String proxyPkg = null;
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
        /*
         * Record the package of a non-public proxy interface so that the
         * proxy class will be defined in the same package.  Verify that
         * all non-public proxy interfaces are in the same package.
         */
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;
                String name = intf.getName();
                int n = name.lastIndexOf('.');
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }
        if (proxyPkg == null) {
            // if no non-public proxy interfaces, use com.sun.proxy package
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }
        /*
         *选择一个代理类的名称,实际就是一个递增的数字
         */
        long num = nextUniqueNumber.getAndIncrement();
        //代理类的全限定类名:com.sun.proxy.$Proxy0 , com.sun.proxy.$Proxy1
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
        /*
         * 生成一个代理类的class文件,也就是我们通常创建的class类
         * 该方法并不是开源的。
         * 我们进去该方法可以看到一个属性saveGeneratedFiles
         * 这个属性用于判断是否将创建的class文件输出到本地,
         * 我们可以在运行时设置该属性,
         * 让它将生成的class文件输出,然后我们反编译这个代理类文件
         */
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
            // 通过类文件创建Class对象,这是一个本地方法(C/C++实现),无法查看
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            /*
             * A ClassFormatError here means that (barring bugs in the
             * proxy class generation code) there was some other
             * invalid aspect of the arguments supplied to the proxy
             * class creation (such as virtual machine limitations
             * exceeded).
             */
            throw new IllegalArgumentException(e.toString());
        }
    }
}

反编译代理类文件

输出代理类文件

要输出该代理类文件,我们有两种方法:

1,我们在运行Test类之前,首先进行一个配置,在红色框框的部分添加这个属性-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

image.png

2,在运行代码之前,设置系统全局属性,添加红色方框部分的代码

image.png

运行代码,代码执行完成之后就会在main()所在类的同级目录下看到创建了一个com.sun.proxy包,该包下面有一个$Proxy0.class类似的class文件,这就是生成的代理类。

反编译代理类

我没有在IDEA上找到合适的插件,网上看到的几个,不知道为什么用不了,唯一一个能用的效果也不好,所有就在网上找了另外一个JD-GUI,无需安装,很好用。

附上下载链接(需要梯子):JD-GUI

因为自己在反编译cglib生成的代理类时发现JD-GUI在反编译一些class文件的某些方法(本人是void方法)出现了反编译不完整的问题(难怪一直看不懂!!!),所以不推荐使用,我现在使用IDEA默认的反编译器(直接在IDEA打开需要反编译的class文件即可),虽然变量名难看了点,但是至少是无误的。

这个网站也可以反编译class文件,效果与IDEA类似,目前在我使用过程中没有发现问题:javare.cn/

我们直接使用软件打开我们生成的代理类 (此代码依然是JD_GUI反编译,因为这个没有错误,所以没有更换,不影响阅读),如下:

package com.sun.proxy;
import com.lhd.proxy.Hello;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Hello {
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;
  public $Proxy0(InvocationHandler paramInvocationHandler) {
    super(paramInvocationHandler);
  }
  public final boolean equals(Object paramObject) {
    try {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  public final void sayHello() {
    try {
      this.h.invoke(this, m3, null);
      return;
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  public final String toString() {
    try {
      return (String)this.h.invoke(this, m2, null);
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  public final int hashCode() {
    try {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  static {
    try {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("com.lhd.proxy.Hello").getMethod("sayHello", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    } catch (NoSuchMethodException noSuchMethodException) {
      throw new NoSuchMethodError(noSuchMethodException.getMessage());
    } catch (ClassNotFoundException classNotFoundException) {
      throw new NoClassDefFoundError(classNotFoundException.getMessage());
    } 
  }
}

通过这个代理类我们可以知道JDK动态代理为什么只能基于接口的代理,因为该代理类继承了Proxy类,而java是不支持多继承的,所有只能通过实现接口来实现代理。

我们可以看到这个代理类实现了我们传递进去的接口Hello,所以代理类里面要实现该接口的方法,也就是sayHello()方法,而这个方法的实现非常简单,其实代理类里面所有的方法都非常简单,就是执行了this.h.invoke()这个方法。

我们分析一下这条语句:

  • this :就是指代理类对象
  • h:h是继承于Proxy类的,是InvocationHandler类型,也就是我们调用newProxyInstance()传递进去的ProxyHello,代理的一个构造函数也是接受这个类型的参数,我们在newProxyInstance方法中获取代理类的构造函数时也是获取带这个类型参数的构造函数,然后通过这个构造函数来创建代理类对象
  • invoke:就是ProxyHello的invoke()方法

当代理类对象执行sayHello()方法时,实际就是执行this.h.invoke(this, m3, null)这个方法,也就是执行了ProxyHello类的对象的invoke()方法,而这个方法的method参数是在最下面获取的:

m3 = Class.forName("com.lhd.proxy.Hello").getMethod("sayHello", new Class[0]);

当我们在调用method.invoke(hello, args)时就是调用的m3这个方法,然后指定一个执行的对象和参数,就可以执行我们需要执行的方法了。

尾声

到目前为止,上面的五个问题就都有答案了。但是具体这个代理类的class文件是如何写入的暂时没有深究,因为没有开源,比较晦涩难懂,而且代码量庞大,就不再深入了。 如果有问题,可以评论区讨论。如果有错误的地方,也请多多包涵并且指正。

目录
相关文章
|
3月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
21天前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
17 0
[Java]静态代理与动态代理(基于JDK1.8)
|
1月前
|
Java
【编程进阶知识】静态代理、JDK动态代理及Cglib动态代理各自存在的缺点及代码示例
本文介绍了三种Java代理模式:静态代理、JDK动态代理和Cglib动态代理。静态代理针对特定接口或对象,需手动编码实现;JDK动态代理通过反射机制实现,适用于所有接口;Cglib动态代理则基于字节码技术,无需接口支持,但需引入外部库。每种方法各有优缺点,选择时应根据具体需求考虑。
18 1
|
3月前
|
Java API 开发者
Jdk动态代理为啥不能代理Class?
该文章主要介绍了JDK动态代理的原理以及为何JDK动态代理不能代理Class。
Jdk动态代理为啥不能代理Class?
|
3月前
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
212 0
|
3月前
|
设计模式 Java C++
揭秘!JDK动态代理VS CGLIB:一场关于Java代理界的‘宫心计’,你站哪队?
【8月更文挑战第24天】Java 动态代理是一种设计模式,允许在不改动原类的基础上通过代理类扩展功能。主要实现方式包括 JDK 动态代理和 CGLIB。前者基于接口,利用反射机制在运行时创建代理类;后者采用继承方式并通过字节码技术生成子类实现类的代理。两者在实现机制、性能及适用场景上有明显差异。JDK 动态代理适用于有接口的场景,而 CGLIB 更适合代理未实现接口的类,尽管性能更优但存在一些限制。开发者可根据需求选择合适的代理方式。
172 0
|
4月前
|
监控 算法 Java
怎么用JDK自带工具进行JVM内存分析
JVM内存分析工具,如`jps`、`jcmd`、`jstat`、`jstack`和`jmap`,是诊断和优化Java应用的关键工具。`jps`列出Java进程,`jcmd`执行诊断任务,如查看JVM参数和线程堆栈,`jstat`监控内存和GC,`jstack`生成线程堆栈信息,而`jmap`则用于生成堆转储文件。这些工具帮助排查内存泄漏、优化内存配置、性能调优和异常分析。例如,`jmap -dump:file=heapdump.hprof &lt;PID&gt;`生成堆转储文件,之后可以用Eclipse Memory Analyzer (MAT)等工具分析。
|
5月前
|
设计模式 Java 程序员
java动态代理(JDK和cglib)
java动态代理(JDK和cglib)
30 0
|
5月前
|
设计模式 Java 程序员
java动态代理(JDK和cglib)
java动态代理(JDK和cglib)
40 0
|
5月前
|
缓存 Java Maven
JDK 动态代理
JDK 动态代理
28 0