写点什么

从源码角度研究 Java 动态代理

发布于: 2021 年 05 月 28 日
从源码角度研究Java动态代理

前言

最近,看了一下关于 RMI(Remote Method Invocation)相关的知识,遇到了一个动态代理的问题,然后就决定探究一下动态代理。


这里先科普一下 RMI。

RMI

像我们平时写的程序,对象之间互相调用方法都是在同一个 JVM 中进行,而 RMI 可以实现一个 JVM 上的对象调用另一个 JVM 上对象的方法,即远程调用。

接口定义

定义一个远程对象接口,实现 Remote 接口来进行标记。


public interface UserInterface extends Remote {    void sayHello() throws RemoteException;}
复制代码

远程对象定义

定义一个远程对象类,继承 UnicastRemoteObject 来实现 Serializable 和 Remote 接口,并实现接口方法。


public class User extends UnicastRemoteObject implements UserInterface {    public User() throws RemoteException {}    @Override    public void sayHello() {        System.out.println("Hello World");    }}
复制代码

服务端

启动服务端,将 user 对象在注册表上进行注册。


public class RmiServer {    public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {        User user = new User();        LocateRegistry.createRegistry(8888);        Naming.bind("rmi://127.0.0.1:8888/user", user);        System.out.println("rmi server is starting...");    }}
复制代码


启动服务端:


客户端

从服务端注册表获取远程对象,在服务端调用 sayHello()方法。


public class RmiClient {    public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException {        UserInterface user = (UserInterface) Naming.lookup("rmi://127.0.0.1:8888/user");        user.sayHello();    }}
复制代码


服务端运行结果:



至此,一个简单的 RMI demo 完成。

动态代理

提出问题

看了看 RMI 代码,觉得 UserInterface 这个接口有点多余,如果客户端使用 Naming.lookup()获取的对象不强转成 UserInterface,直接强转成 User 是不是也可以,于是试了一下,就报了以下错误:



似曾相识又有点陌生的 $Proxy0,翻了翻尘封的笔记找到了是动态代理的知识点,寥寥几笔带过,所以决定梳理一下动态代理,重新整理一份笔记。

动态代理 Demo

接口定义

public interface UserInterface {    void sayHello();}
复制代码

真实角色定义

public class User implements UserInterface {    @Override    public void sayHello() {        System.out.println("Hello World");    }}
复制代码

调用处理类定义

代理类调用真实角色的方法时,其实是调用与真实角色绑定的处理类对象的 invoke()方法,而 invoke()调用的是真实角色的方法。


这里需要实现 InvocationHandler 接口以及 invoke()方法。


public class UserHandler implements InvocationHandler {    private User user;    public UserProxy(User user) {        this.user = user;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        System.out.println("invoking start....");        method.invoke(user);        System.out.println("invoking stop....");        return user;    }}
复制代码

执行类

public class Main {    public static void main(String[] args) {        User user = new User();        // 处理类和真实角色绑定        UserHandler userHandler = new UserHandler(user);        // 开启将代理类class文件保存到本地模式,平时可以省略        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");        // 动态代理生成代理对象$Proxy0        Object o = Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{UserInterface.class}, userHandler);        // 调用的其实是invoke()        ((UserInterface)o).sayHello();    }
复制代码


运行结果:



这样动态代理的基本用法就学完了,可是还有好多问题不明白。


  1. 动态代理是怎么调用的 invoke()方法?

  2. 处理类 UserHandler 有什么作用?

  3. 为什么要将类加载器和接口类数组当作参数传入 newProxyInstance?


假如让你去实现动态代理,你有什么设计思路?

猜想

动态代理,是不是和静态代理,即设计模式的代理模式有相同之处呢?


简单捋一捋代理模式实现原理:真实角色和代理角色共同实现一个接口并实现抽象方法 A,代理类持有真实角色对象,代理类在 A 方法中调用真实角色对象的 A 方法。在 Main 中实例化代理对象,调用其 A 方法,间接调用了真实角色的 A 方法。


实现代码


// 接口和真实角色对象就用上面代码// 代理类,实现UserInterface接口public class UserProxy implements UserInterface {    // 持有真实角色对象    private User user = new User();    @Override    public void sayHello() {        System.out.println("invoking start....");        // 在代理对象的sayHello()里调用真实角色的sayHello()        user.sayHello();        System.out.println("invoking stop....");    }}// 运行类public class Main {    public static void main(String[] args) {        // 实例化代理角色对象        UserInterface userProxy = new UserProxy();        // 调用了代理对象的sayHello(),其实是调用了真实角色的sayHello()        userProxy.sayHello();    }
复制代码


拿开始的动态代理代码和静态代理比较,接口、真实角色都有了,区别就是多了一个 UserHandler 处理类,少了一个 UserProxy 代理类。


接着对比一下两者的处理类和代理类,发现 UserHandler 的 invoke()和 UserProxy 的 sayHello()这两个方法的代码都是一样的。那么,是不是新建一个 UserProxy 类,然后实现 UserInterface 接口并持有 UserHandler 的对象,在 sayHello()方法中调用 UserHandler 的 invoke()方法,就可以动态代理了。


代码大概就是这样的


// 猜想的代理类结构,动态代理生成的代理是com.sun.proxy.$Proxy0public class UserProxy implements UserInterface{    // 持有处理类的对象    private InvocationHandler handler;    public UserProxy(InvocationHandler handler) {        this.handler = handler;    }    // 实现sayHello()方法,并调用invoke()    @Override    public void sayHello() {        try {            handler.invoke(this, UserInterface.class.getMethod("sayHello"), null);        } catch (Throwable throwable) {            throwable.printStackTrace();        }    }}// 执行类public static void main(String[] args) {        User user = new User();        UserHandler userHandler = new UserHandler(user);        UserProxy proxy = new UserProxy(userHandler);        proxy.sayHello();    }
复制代码


输出结果:



上面的代理类代码是写死的,而动态代理是当你调用 Proxy.newProxyInstance()时,会根据你传入的参数来动态生成这个代理类代码,如果让我实现,会是以下这个流程。


  1. 根据你传入的 Class[]接口数组,代理类会来实现这些接口及其方法(这里就是 sayHello()),并且持有你传入的 userHandler 对象,使用文件流将预先设定的包名、类名、方法名等一行行代码写到本地磁盘,生成 $Proxy0.java 文件

  2. 使用编译器将Proxy0.class

  3. 根据你传入的 ClassLoader 将 $Proxy0.class 加载到 JMV 中

  4. 调用 Proxy.newProxyInstance()就会返回一个 $Proxy0 的对象,然后调用 sayHello(),就执行了里面 userHandler 的 invoke()


以上就是对动态代理的一个猜想过程,下面就通过 debug 看看源码是怎么实现的。


在困惑的日子里学会拥抱源码

调用流程图

这里先用 PPT 画一个流程图,可以跟着流程图来看后面的源码。



从 newProxyInstance()设置断点


newProxyInstance()

newProxyInstance()代码分为上下两部分,上部分是获取类Proxy0 对象。


上部分代码



从名字看就知道 getProxyClass0()是核心方法,step into

getProxyClass0()


里面调用了 WeakCache 对象的 get()方法,这里暂停一下 debug,先讲讲 WeakCache 类。

WeakCache

顾名思义,它是一个弱引用缓存。那什么是是弱引用呢,是不是还有强引用呢?

弱引用

WeakReference 就是弱引用类,作为包装类来包装其他对象,在进行 GC 时,其中的包装对象会被回收,而 WeakReference 对象会被放到引用队列中。


举个栗子:


 // 这就是强引用,只要不写str1 = null,str1指向的这个字符串不就会被垃圾回收 String str1 = new String("hello"); ReferenceQueue referenceQueue = new ReferenceQueue(); // 只要垃圾回收,这个str2里面包装的对象就会被回收,但是这个弱引用对象不会被回收,即word会被回收,但是str2指向的弱引用对象不会 // 每个弱引用关联一个ReferenceQueue,当包装的对象被回收,这个弱引用对象会被放入引用队列中 WeakReference<String> str2 = new WeakReference<>(new String("world"), referenceQueue); // 执行gc System.gc(); Thread.sleep(3); // 输出被回收包装对象的弱引用对象:java.lang.ref.WeakReference@2077d4de // 可以debug看一下,弱引用对象的referent变量指向的包装对象已经为null System.out.println(referenceQueue.poll());
复制代码

WeakCache 的结构

其实整个 WeakCache 的都是围绕着成员变量 map 来工作的,构建了一个一个<K,<K,V>>格式的二级缓存,在动态代理中对应的类型是<类加载器, <接口 Class, 代理 Class>>,它们都使用了弱引用进行包装,这样在垃圾回收的时候就可以直接回收,减少了堆内存占用。


// 存放已回收弱引用的队列private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();// 使用ConcurrentMap实现的二级缓存结构private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();// 可以不关注这个,这个是用来标识二级缓存中的value是否存在的,即Supplier是否被回收private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>();// 包装传入的接口class,生成二级缓存的Keyprivate final BiFunction<K, P, ?> subKeyFactory = new KeyFactory();// 包装$Proxy0,生成二级缓存的Valueprivate final BiFunction<K, P, V> valueFactory = new ProxyClassFactory();
复制代码

WeakCache 的 get()

回到 debug,接着进入 get()方法,看看 map 二级缓存是怎么生成 KV 的。


 public V get(K key, P parameter) {        Objects.requireNonNull(parameter);        // 遍历refQueue,然后将缓存map中对应的失效value删除        expungeStaleEntries();        // 以ClassLoader为key,构建map的一级缓存的Key,是CacheKey对象        Object cacheKey = CacheK.valueOf(key, refQueue);        // 通过Key从map中获取一级缓存的value,即ConcurrentMap        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);        if (valuesMap == null) {          // 如果Key不存在,就新建一个ConCurrentMap放入map,这里使用的是putIfAbsent          // 如果key已经存在了,就不覆盖并返回里面的value,不存在就返回null并放入Key          // 现在缓存map的结构就是ConCurrentMap<CacheKey, ConCurrentMap<Object, Supplier>>            ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());            // 如果其他线程已经创建了这个Key并放入就可以复用了            if (oldValuesMap != null) {                valuesMap = oldValuesMap;            }        }        // 生成二级缓存的subKey,现在缓存map的结构就是ConCurrentMap<CacheKey, ConCurrentMap<Key1, Supplier>>        // 看后面的<生成二级缓存Key>!!!        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));        // 根据二级缓存的subKey获取value        Supplier<V> supplier = valuesMap.get(subKey);        Factory factory = null;        // !!!直到完成二级缓存Value的构建才结束,Value是弱引用的$Proxy0.class!!!        while (true) {            // 第一次循环:suppiler肯定是null,因为还没有将放入二级缓存的KV值            // 第二次循环:这里suppiler不为null了!!!进入if            if (supplier != null) {                // 第二次循环:真正生成代理对象,                // 往后翻,看<生成二级缓存Value>,核心!!!!!                // 看完后面回到这里:value就是弱引用后的$Proxy0.class                V value = supplier.get();                if (value != null) {                  // 本方法及上部分的最后一行代码,跳转最后的<构建$Proxy对象>                    return value;                }            }             // 第一次循环:factory肯定为null,生成二级缓存的Value            if (factory == null) {                factory = new Factory(key, parameter, subKey, valuesMap);            }            // 第一次循环:将subKey和factory作为KV放入二级缓存            if (supplier == null) {                supplier = valuesMap.putIfAbsent(subKey, factory);                if (supplier == null) {                    // 第一次循环:赋值之后suppiler就不为空了,记住!!!!!                    supplier = factory;                }            }            }        }    }
复制代码

生成二级缓存 Key

在 get()中调用 subKeyFactory.apply(key, parameter),根据你 newProxyInstance()传入的接口 Class[]的个数来生成二级缓存的 Key,这里我们就传入了一个 UserInterface.class,所以就返回了 Key1 对象。



不论是 Key1、Key2 还是 KeyX,他们都继承了 WeakReference,都是包装对象是 Class 的弱引用类。这里看看 Key1 的代码。


生成二级缓存 Value

在上面的 while 循环中,第一次循环只是生成了一个空的 Factory 对象放入了二级缓存的 ConcurrentMap 中。


在第二次循环中,才开始通过 get()方法来真正的构建 value。


别回头,接着往下看。

Factory.get()生成弱引用 value

CacheValue 类是一个弱引用,是二级缓存的 Value 值,包装的是 class,在这里就是 $Proxy0.class,至于这个类如何生成的,根据下面代码注释一直看完 Class 文件的生成


public synchronized V get() {            // 检查是否被回收,如果被回收,会继续执行上面的while循环,重新生成Factory            Supplier<V> supplier = valuesMap.get(subKey);            if (supplier != this) {                return null;            }            // 这里的V的类型是Class            V value = null;            // 这行是核心代码,看后面<class文件的生成>,记住这里返回的是Class            value = Objects.requireNonNull(valueFactory.apply(key, parameter));            // 将Class对象包装成弱引用            CacheValue<V> cacheValue = new CacheValue<>(value);            // 回到上面<WeakCache的get()方法>V value = supplier.get();            return value;        }    }
复制代码


Class 文件的生成

包名类名的定义与验证

进入 valueFactory.apply(key, parameter)方法,看看 class 文件是怎么生成的。


 private static final String proxyClassNamePrefix = "$Proxy";
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); // 遍历你传入的Class[],我们只传入了UserInterface.class for (Class<?> intf : interfaces) { Class<?> interfaceClass = null; // 获取接口类 interfaceClass = Class.forName(intf.getName(), false, loader); // 这里就很明确为什么只能传入接口类,不是接口类会报错 if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } String proxyPkg = null; int accessFlags = Modifier.PUBLIC | Modifier.FINAL; for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); // 验证接口是否是public,不是public代理类会用接口的package,因为只有在同一包内才能继承 // 我们的UserInterface是public,所以跳过 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"); } } } // 如果接口类是public,则用默认的包 if (proxyPkg == null) { // PROXY_PACKAGE = "com.sun.proxy"; proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } // 原子Int,此时num = 0 long num = nextUniqueNumber.getAndIncrement(); // com.sun.proxy.$Proxy0,这里包名和类名就出现了!!! String proxyName = proxyPkg + proxyClassNamePrefix + num; // !!!!生成class文件,查看后面<class文件写入本地> 核心!!!! byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); // !!!看完下面再回来看这行!!!! // 获取了字节数组之后,获取了class的二进制流将类加载到了JVM中 // 并且返回了$Proxy0.class,返回给Factory.get()来包装 return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length); } } }
复制代码


defineClass0()是 Proxy 类自定义的类加载的 native 方法,会获取 class 文件的二进制流加载到 JVM 中,以获取对应的 Class 对象,这一块可以参考 JVM 类加载器。

class 文件写入本地

generateProxyClass()方法会将 class 二进制文件写入本地目录,并返回 class 文件的二进制流,使用你传入的类加载器加载,这里你知道类加载器的作用了么


 public static byte[] generateProxyClass(final String name,                                            Class[] interfaces)    {        ProxyGenerator gen = new ProxyGenerator(name, interfaces);        // 生成class文件的二进制,查看后面<生成class文件二进制>        final byte[] classFile = gen.generateClassFile();        // 将class文件写入本地            if (saveGeneratedFiles) {            java.security.AccessController.doPrivileged(            new java.security.PrivilegedAction<Void>() {                public Void run() {                    try {                        FileOutputStream file =                            new FileOutputStream(dotToSlash(name) + ".class");                        file.write(classFile);                        file.close();                        return null;                    } catch (IOException e) {                        throw new InternalError(                            "I/O exception saving generated file: " + e);                    }                }            });        }        // 返回$Proxy0.class字节数组,回到上面<class文件生成>        return classFile;    }
复制代码

生成 class 文件二进制流

generateClassFile()生成 class 文件,并存放到字节数组,可以顺便学一下 class 结构,这里也体现了你传入的 class[]的作用


    private byte[] generateClassFile() {        // 将hashcode、equals、toString是三个方法放入代理类中        addProxyMethod(hashCodeMethod, Object.class);        addProxyMethod(equalsMethod, Object.class);        addProxyMethod(toStringMethod, Object.class);        for (int i = 0; i < interfaces.length; i++) {            Method[] methods = interfaces[i].getMethods();            for (int j = 0; j < methods.length; j++) {              // 将接口类的方法放入新建的代理类中,这里就是sayHello()                addProxyMethod(methods[j], interfaces[i]);            }        }        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {            checkReturnTypes(sigmethods);        }        // 给代理类增加构造方法        methods.add(generateConstructor());        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {            for (ProxyMethod pm : sigmethods) {                   // 将上面的四个方法都封装成Method类型成员变量                    fields.add(new FieldInfo(pm.methodFieldName,                        "Ljava/lang/reflect/Method;",                         ACC_PRIVATE | ACC_STATIC));                    // generate code for proxy method and add it                    methods.add(pm.generateMethod());                }            }        // static静态块构造        methods.add(generateStaticInitializer());        cp.getClass(dotToSlash(className));        cp.getClass(superclassName);        for (int i = 0; i < interfaces.length; i++) {            cp.getClass(dotToSlash(interfaces[i].getName()));        }        cp.setReadOnly();        ByteArrayOutputStream bout = new ByteArrayOutputStream();        DataOutputStream dout = new DataOutputStream(bout);        // !!!核心点来了!这里就开始构建class文件了,以下都是class的结构,只写一部分        try {               // u4 magic,class文件的魔数,确认是否为一个能被JVM接受的class            dout.writeInt(0xCAFEBABE);            // u2 minor_version,0            dout.writeShort(CLASSFILE_MINOR_VERSION);            // u2 major_version,主版本号,Java8对应的是52;            dout.writeShort(CLASSFILE_MAJOR_VERSION);            // 常量池            cp.write(dout);            // 其他结构,可参考class文件结构            dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);            dout.writeShort(cp.getClass(dotToSlash(className)));            dout.writeShort(cp.getClass(superclassName));            dout.writeShort(interfaces.length);            for (int i = 0; i < interfaces.length; i++) {                dout.writeShort(cp.getClass(                    dotToSlash(interfaces[i].getName())));            }            dout.writeShort(fields.size());            for (FieldInfo f : fields) {                f.write(dout);            }            dout.writeShort(methods.size());                       for (MethodInfo m : methods) {                m.write(dout);            }            dout.writeShort(0);         } catch (IOException e) {            throw new InternalError("unexpected I/O Exception", e);        }        // 将class文件字节数组返回        return bout.toByteArray();    }
复制代码


构建 $Proxy 对象

newProxyInstance()上半部分经过上面层层代码调用,获取了 $Proxy0.class,接下来看下部分代码:



cl 就是上面获取的 Proxy0.class,h 就是上面传入的 userHandler,被当做构造参数来创建 $Proxy0 对象。然后获取这个动态代理对象,调用 sayHello()方法,相当于调用了 UserHandler 的 invoke(),这里就是 UserHandler 的作用

$Proxy.class 文件

我们开启了将代理 class 写到本地目录的功能,在项目下的 com/sum/proxy 目录下找到了 $Proxy0 的 class 文件。


看一下反编译的 class


package com.sun.proxy;
import com.test.proxy.UserInterface;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 UserInterface { private static Method m1; private static Method m3; private static Method m2; private static Method m0;
public $Proxy0(InvocationHandler var1) throws { super(var1); }
public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } }
public final void sayHello() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (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")); m3 = Class.forName("com.test.proxy.UserInterface").getMethod("sayHello"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } }}
复制代码

结语

上面就是动态代理源码的调试过程,与之前的猜想的代理类的生成过程比较,动态代理是直接生成 class 文件,省去了 java 文件和编译这一块。


刚开始看可能比较绕,跟着注释及跳转指引,耐心多看两遍就明白了。动态代理涉及的知识点比较多,我自己看的时候,在 WeakCache 这一块纠结了一阵,其实把它当成一个两层的 map 对待即可,只不过里面所有的 KV 都被弱引用包装。




95 后小程序员,写的都是日常工作中的亲身实践,置身于初学者的角度从 0 写到 1,详细且认真。文章会在公众号 [入门到放弃之路] 首发,期待你的关注。



发布于: 2021 年 05 月 28 日阅读数: 886
用户头像

公众号:入门到放弃之路 2021.05.23 加入

公众号:入门到放弃之路。自学Java、python、大数据。

评论

发布
暂无评论
从源码角度研究Java动态代理