JDK动态代理浅析

156 阅读7分钟
目录结构
  • JDK代理的用法
  • 实现原理
  • 总结
JDK代理用法

用法可以按照如下步骤来完成:

1、给定一个接口(Target接口)

2、以及一个接口的实现类(TargetImpl类)

3、通过Proxy相关的API,可以获取一个实现(TargetImpl类)的代理对象。

4、并且通过Proxy相关的API,可以在代理对象中增加一些TargetImp所没有的功能。

接着我们把上面的步骤一步步完成:

首先Target接口:

public interface Target {
    void sayHello();
}

然后有TargetImpl类:

public class TargetImp implements Target {
    @Override
    public void sayHello(){
        System.out.println("hello");
    }
}

那么被代理的类都有了,那我们就可以生成代理对象来将TargetImpl类进行增强,主要是通过过Proxy.newProxyInstance静态方法来生成代理对象,并实现InvocationHandler类来添加增强的操作。

public static void main(String[] args) {
    // 被代理的对象
    Target TargetImpl = new TargetImp();
    // Proxy.newProxyInstance静态方法返回一个实现了Target接口的对象,该对象就是代理对象(proxyOfTarget)
    // InvocationHandler是代理对象调用转发类,要求实现其invoke方法。
    // 当proxyOfTarget对象调用方法时,实际上会将调用转到invoke方法。
    // 那么invoke方法可以先做一些增强/代理的操作,然后再调用实际的对象。
    Target proxyOfTarget = (Target) Proxy.newProxyInstance(Target.class.getClassLoader(), new Class[]{Target.class}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 在被代理对象之前做一些增强操作
            System.out.println("this is enhancing code!");
            // 调用被代理对象,实现功能,并返回结果。
            return method.invoke(TargetImpl, args);
        }
    });
    proxyOfTarget.sayHello();
}

运行main方法得到:

image.png

至此我们就通过Proxy的newProxyInstance获得了TargetImpl对象的代理对象。 并且代理的操作都封装在InvocationHandler的invoke方法内。Proxy的基本使用就是如此简单,可以看出一个明显的特点:就是被代理类得有一个接口,因为newProxyInstance返回的是Object对象,需要通过接口来将其转化。究其根因还是和Proxy的内部实现机制有关。

实现原理
代理对象的类

其实代理对象也有个对应的类(代理类),只是这个代理类是Proxy自动生成的,我们在代码中看不到。Proxy生成代理类之后,根据这个类创建了代理对象并返回。接下来我们来看看这个代理类,进一步看看JDK代理的原理。

编译程序之前加上虚拟机参数:

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

image.png

即可将自动生成的代理类保存下来:

image.png

代理类名称为$Proxy0,内容如下(为了方便展示,我将部分代码省略):

public final class $Proxy0 extends Proxy implements Target {
    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) {
            ....
        }
    }

    // 重点
    public final void sayHello() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            ...
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.company.Target").getMethod("sayHello");
        } catch (NoSuchMethodException var2) {
            ....
        }
    }
}

可以看出$Proxy0类继承了Proxy类并实现了Target接口,并实现了sayHello方法,除此之外还有实现了toString等Object的方法。因此我们代理对象调用的sayHello就是这个类的方法。里面的实现逻辑很简单:

super.h.invoke(this, m3, (Object[])null);

其中父类的h成员是:

image.png

正是调用newProxyInstance方法传进来的:

image.png

也就是我们生成代理对象时实现的InvocationHandler,而调用sayHello方法就是调用invoke方法。

至此我们明白了:Proxy.newProxyInstance方法为我们生成了一个代理类,这个代理类实现了被代理类的接口,并且所有实现就是简单地把调用转发给invoke方法,而invoke方法正是由我们所实现的。接下来我们看看JDK如何生成这个代理类。

生成代理类

以Proxy.newProxyInstance方法为入口,我将一些关键的代码截取出来,形成整个动作的逻辑。(因为大部分操作只涉及reflect类,没有native方法,可以只看java代码就可以梳理整个逻辑)在次此前需要理清两个概念:

1、生成代理类($Proxy0类)其实就是生成代理类的class字节码,并且该字节是不变的,不用每次都生成。因为被代理类接口(Target接口)是固定,因此自动生成的代理类($Proxy0类)里面的方法就是固定不变的,也就是一些object方法加上Target接口的方法(变化的部分都保留在super.h成员变量上,这个变量在父类Proxy上,可以通过$Proxy0的构造方法传入)。那么对于一个Target接口,我们可以只生成一次class字节码,并且转为Class对象将其缓存起来,以后下次调用生成Target接口的代理对象时,直接根据这个Class对象创建对象即可。

2、如何自动生成class字节码:JDK里面的内部实现是根据Target接口的方法直接拼接$Proxy0类的class字节码,并输出到一个byte数组中,之后创建对象的时候提供类加载器实例化即可。在生成的过程中,如果虚拟机参数saveGeneratedFiles为True,就落盘保存一下。生成字节码的具体实现在sun.misc.ProxyGenerator.generateClassFile方法上,其核心就是输出一个符合class规范的字节数组,并且包含了所有Target接口的方法,其每个方法的实现都是一样的:直接转给InvocationHandler对象处理。

创建代理对象实例的方法:

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

    // 获取代理类的Class对象
    Class<?> cl = getProxyClass0(loader, intfs);
    ...
    
    // 拿到构造器
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    ...
    
    // 根据传入的invocationHandler对象创建代理对象,并放回
    return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        ...
    }

Proxy中getProxyClass0方法

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    //
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    return proxyClassCache.get(loader, interfaces);
}

可以看到实际是从proxyClassCache对象中拿到的Class对象。proxyClassCache对象是 WeakCache类的实例,其中的get方法就是检查缓存(ConrruentHashMap对象)中是否有代理类的Class对象,如果有则立即返回,如果没有则新建。我们直接看新建部分的代码: 新建代理类Class对象的是ProxyClassFactory类:

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    // 代理类名的前缀
    private static final String proxyClassNamePrefix = "$Proxy";

    // 代理类名称的计数器
    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) {
            // 检查该接口是否可以通过给定的ClassLoader来加载
            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");
            }
            // 是否为接口类型
            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;     // package to define proxy class in
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

        /*
         * 非public接口,代理类的包名与接口的包名相同
         */
        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) {
            // public接口,使用com.sun.proxy package包名
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }

        // 拼接代理类名
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;

        // 调用方法生成代理类的class字节码数组
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
        try {
            // 让加载器loader加载,生成Class对象返回。
            return defineClass0(loader, proxyName,
                    proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            throw new IllegalArgumentException(e.toString());
        }
    }
}

ProxyGenerator.generateProxyClass方法如下:

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
    ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    // 生成字节码数组
    final byte[] var4 = var3.generateClassFile();
    if (saveGeneratedFiles) {
        // 异步保留class文件
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                try {
                    ...
                    Files.write(var2, var4, new OpenOption[0]);
                    return null;
                } catch (IOException var4x) {
                    throw new InternalError("I/O exception saving generated file: " + var4x);
                }
            }
        });
    }
    return var4;
}

generateClassFile方法的部分代码如下,可以看出字节码拼凑的过程:

private byte[] generateClassFile() {
        ...
        ByteArrayOutputStream var13 = new ByteArrayOutputStream();
        DataOutputStream var14 = new DataOutputStream(var13);

        try {
            var14.writeInt(-889275714);
            var14.writeShort(0);
            var14.writeShort(49);
            this.cp.write(var14);
            var14.writeShort(this.accessFlags);
            var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
            var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
            var14.writeShort(this.interfaces.length);
            Class[] var17 = this.interfaces;
            int var18 = var17.length;

            for(int var19 = 0; var19 < var18; ++var19) {
                Class var22 = var17[var19];
                var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
            }
            ...
            
            var14.writeShort(this.methods.size());
            var15 = this.methods.iterator();

            while(var15.hasNext()) {
                ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
                var21.write(var14);
            }

            var14.writeShort(0);
            return var13.toByteArray();
        } catch (IOException var9) {
            throw new InternalError("unexpected I/O Exception", var9);
        }
    
}
总结

1、实际上JDK动态代理就是帮我们自动生成了一个实现了被代理类(或被代理类接口)的所有方法的代理类。该代理类实现方法的方式很简单,直接将方法转给InvocationHandler对象的invoke方法。而InvocationHandler对象由用户创建代理对象的时候传入,由用户掌控。用户可以将其转发给被代理类的实际对象,也可以在转发前做一些增强操作。

2、生成代理类的过程是运行时发生的,并且自动生成的Class字节码不会保存,对比静态代理方法多花费写时间。但对比静态代理需要手动编写代理类,JDK动态代理可方便太多了。

3、可以看出JDK代理的方式是依赖被代理对象是有接口的,从而根据其接口的方法来生成代理类的字节码,并且只需要生成一次即可。如果被代理对象没有接口,也就是没有通以的方法集合,那么每个代理类字节码都不一样,那么需用CGLIB库来实现,该库对字节码的操作更加灵活。