手记

Java设计模式(14)----------动态代理原理源码分析

上篇文章《Java设计模式(13)----------代理模式》中,介绍了两种代理模式(静态代理和动态代理)的应用场景和实际应用案例。本篇文章中,对动态代理的原理做进行深入的分析。

关于动态代理,初看可能会比较费解,大概有如下几个疑问:

  • 代理是怎么形成的,代理类在哪里?
  • TimeHandler类是做什么用的,在哪里被调用了?
  • 客户端调用的时候,写的是调用m.move();,程序是如何执行到了TimeHandler对象的invoke方法中了呢?

这篇文章,主要针对如上几个疑问进行展开。

首先声明一点,案例中的TimeHandler并不是代理类,而是代理依赖的一个类而已。真正的代理类,是JDK直接生成的字节码并进行加载的,所以对用户是不可见的。

生成代理类的代码是这个:
Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(), h);
这里向newProxyInstance传递了三个参数,分别是原始类的加载器,原始类实现的接口(Moveable),TimeHandler类的对象。

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        Objects.requireNonNull(h);

        final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();

        /*
         * Look up or generate the designated proxy class and its constructor.
         */
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

        return newProxyInstance(caller, cons, h);

newProxyInstance的代码实现比较清晰,通过getProxyConstructor方法创建了代理类,并返回了该类的构造方法。之后使用反射,生成代理类的对象并返回。

private static Constructor<?> getProxyConstructor(Class<?> caller,
                                                      ClassLoader loader,
                                                      Class<?>... interfaces)
    {
        // optimization for single interface
        if (interfaces.length == 1) {
            Class<?> intf = interfaces[0];
            if (caller != null) {
                checkProxyAccess(caller, loader, intf);
            }
            return proxyCache.sub(intf).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        } else {
            // interfaces cloned
            final Class<?>[] intfsArray = interfaces.clone();
            if (caller != null) {
                checkProxyAccess(caller, loader, intfsArray);
            }
            final List<Class<?>> intfs = Arrays.asList(intfsArray);
            return proxyCache.sub(intfs).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        }
    }

getProxyConstructor函数中,可以看到if-else分支,这里是对单接口的情况做了代码优化。我们主要关注其代理类的生成部分,就是new ProxyBuilder(ld, clv.key()).build()

(ld, clv) -> new ProxyBuilder(ld, clv.key()).build() 这种写法是lambda表达式的语法,非常精简。

        Constructor<?> build() {
            Class<?> proxyClass = defineProxyClass(module, interfaces);
            final Constructor<?> cons;
            try {
                cons = proxyClass.getConstructor(constructorParams);
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
            return cons;
        }

在build方法中,从语意上可以看到代理类proxyClassdefineProxyClass方法中生成。之后通过反射并根据构造函数的参数,获取到代理类的构造方法并返回。

这里的参数为private static final Class<?>[] constructorParams = { InvocationHandler.class };。所以看到这里,应该明白了TimeHandler的作用了,就是作为代理类的构造方法的一个参数,也就是代理类依赖的对象。到这里,也就找到了文章开始处提出的问题二的答案。这里可以猜测一下,代理类中的对应接口的方法,应该是调用的TimeHandler类的invoke方法,通过控制invoke的参数,来调用不同的方法。

defineProxyClass方法中,最关键的代码如下:

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
            try {
                Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
                                                 0, proxyClassFile.length,
                                                 loader, null);
                reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
                return pc;
            } 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());
            }

这里先生成字节码,之后将其加载到内存中。

    static byte[] generateProxyClass(final String name,
                                     Class<?>[] interfaces,
                                     int accessFlags)
    {
        ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
        final byte[] classFile = gen.generateClassFile();

        if (saveGeneratedFiles) {
            java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction<Void>() {
                public Void run() {
                    try {
                        int i = name.lastIndexOf('.');
                        Path path;
                        if (i > 0) {
                            Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));
                            Files.createDirectories(dir);
                            path = dir.resolve(name.substring(i+1, name.length()) + ".class");
                        } else {
                            path = Paths.get(name + ".class");
                        }
                        Files.write(path, classFile);
                        return null;
                    } catch (IOException e) {
                        throw new InternalError(
                            "I/O exception saving generated file: " + e);
                    }
                }
            });
        }

        return classFile;
    }

generateProxyClass方法中,先生成了ProxyGenerator对象,然后调用对象的generateClassFile生成字节码。可以看到其中有个saveGeneratedFiles的标志位,表示是否需要保存生成的字节码文件。在generateClassFile方法中,创建了ProxyMethod,字段表和方法表集合,并将内容按照字节码的规范写入到流中。至此代理类就生成了,文章开始处的问题一就回答完毕了

此处代码中可以根据语意看到会使用InvocationHandler的方法,目前对java的class文件的格式还不够了解,有机会研究一下。

刚刚说到代码中是存在打印生成的代理类的逻辑的,就是saveGeneratedFiles标志位,在该变量定义处可以看到:

private static final boolean saveGeneratedFiles =
        java.security.AccessController.doPrivileged(
            new GetBooleanAction(
                "jdk.proxy.ProxyGenerator.saveGeneratedFiles")).booleanValue();

表示该变量是由jdk.proxy.ProxyGenerator.saveGeneratedFiles控制的。所以我们在代码中只需要指定这个值为true就可以了,代码如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) throws Exception{
        System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
        Car car = new Car();
        Class<?> cls = car.getClass();

        InvocationHandler h = new TimeHandler(car);
        Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),cls.getInterfaces(), h);
        m.move();
        System.out.println("");
        m.move_back();
        System.out.println("");System.out.println("");
    }
}

运行之后,就可以在工程目录下找到对应的代理类的class文件啦

使用idea打开,可以看到对应的java代码如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sun.proxy;

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 Moveable {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    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 move() throws Exception {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (Exception | 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 void move_back() throws Exception {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (Exception | 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("Moveable").getMethod("move");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("Moveable").getMethod("move_back");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

代理类解读:

  • 代理类的构造方法,接收一个InvocationHandler对象,调用父类Proxy的初始化方法,使用该对象为依赖h的对象赋值。
  • 以move方法为例,我们可以看到,代理类实现了Moveable接口,在move方法的实现中,调用了InvocationHandler对象的invoke方法,并且其中第二个参数为m3对象。而m3对象是静态块中通过反射获取到的Moveable接口的move方法。所以,此处就回答的文章开始处的问题三。

到这里,动态代理的原理就非常清晰了,代理类是jdk直接以字节码的形式生成出来的,继承Proxy类,实现客户端定义的接口。因为Proxy依赖InvocationHandler实现类,所以代理类也同样依赖。对于接口中定义的函数move,代理类中也会实现,其逻辑是调用InvocationHandler类中的invoke方法,并将方法move方法作为invoke的一个参数。在invoke方法中,可以进行功能扩展,进而执行invoke参数中的方法,完成对原始对象的move方法的调用。不得不佩服java源码的作者们的深厚功力,能够设计出如此高扩展的结构,自己需要学习和实践的还有很多。

5人推荐
随时随地看视频
慕课网APP

热门评论

道可老师,写的日记很有条理,,,,棒极了

查看全部评论