JVM字节码(七)

动态代理

日常开发中,我们经常会遇到一些与我们业务无关却又无法避免的需求,比如:统计每个接口的访问量、给每个接口打印日志……等等,这些都是很常见的需求。如果在每个接口里编写增加访问量或者打印日志的代码,势必会引入一些冗余且无关业务的代码。

因此,Java提出动态代理的概念,将我们的主业务放在被代理类中执行,而与业务关系并非不大但的代码则放在调用句柄InvocationHandler中执行,调用句柄会通过反射的方式,调用被代理类的方法。通过调用句柄创建代理类,来实现动态代理。为了实现动态代理,我们需要了解:java.lang.reflect.InvocationHandler调用句柄接口和java.lang.reflect.Proxy类。

接口:

package com.leolin.jvm.bytecode;

public interface Subject {
    void request();
}

  

被代理类,即执行主业务的类:

package com.leolin.jvm.bytecode;

public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("From RealSubject");//主业务
    }
}

    

调用句柄:

package com.leolin.jvm.bytecode;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class SubjectHandler implements InvocationHandler {
    private Object object;

    public SubjectHandler(Object object) {
        this.object = object;    //被代理对象,这里传入RealSubject实例
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //代理对象在执行方法时,会执行调用句柄的invoke方法,我们在被代理对象执行方法的前后,添加打印操作
        System.out.println("Before Call method:" + method);
        //通过反射调用被代理对象的方法
        Object result = method.invoke(this.object, args);
        System.out.println("After Call method:" + method);
        return result;
    }
}

     

调用句柄一般执行诸如:打印日志、统计接口访问量、打开数据库连接和释放数据库连接等等。句柄中我们声明了一个成员变量object,在创建句柄时,我们会把之前的RealSubject实例传进去,当执行代理对象的调用方法时,会转而执行句柄的invoke方法,而method.invoke(Object obj, Object... args)就是对被代理对象进行方法调用,我们可以在这一句的前后添加我们要执行的业务逻辑。

客户端:

package com.leolin.jvm.bytecode;

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

public class Client {
    public static void main(String[] args) {
        //主业务对象(被代理对象)
        RealSubject rs = new RealSubject();
        //通过被代理对象构造出调用句柄
        InvocationHandler ds = new SubjectHandler(rs);
        Class<?> cls = rs.getClass();
        /*
         * 通过Proxy类生成被代理对象,这里需要传入三个参数:
         * 第一个参数:被代理对象的类加载器
         * 第二个参数:被代理对象所实现的接口
         * 第三个参数:封装被代理对象的句柄
         * */
        Subject subject = (Subject) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), ds);
        subject.request();
    }
}

    

运行代码,得到如下结果:

Before Call method:public abstract void com.leolin.jvm.bytecode.Subject.request()
From RealSubject
After Call method:public abstract void com.leolin.jvm.bytecode.Subject.request()

    

可以看到,我们通过Proxy类所生成的代理对象,在执行request()的时候,会在调用被代理对象RealSubject.request()的前后,执行我们所编写的打印代码。

这里,我们看看在Proxy.newProxyInstance()方法中,都发生了什么:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        //com.sun.proxy.$Proxy0程序运行期动态创建出来
        System.out.println(subject.getClass());
        System.out.println(subject.getClass().getSuperclass());   

   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);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        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);
        }
    }	

  

我们重点关注第22、32、42行上。22行,我们通过getProxyClass0获取到代理类的class对象,32行我们通过class对象获取到一个构造方法,这个构造方法要求传入一个调用句柄参数,而在42行,我们将之前传入的调用句柄作为参数,传给构造方法,生成代理对象。32行和42行都好理解,于是我们又将重点转移到22行,getProxyClass0方法是如何生成代理对象的class对象呢?我们来看下getProxyClass0的方法:

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

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

  

getProxyClass0所生成的代理类,要求实现的接口数不超过65535,一般业务开发不会达到这个量级,接下来就是从proxyClassCache中获取一个class对象,我们重点看上面代码的注释:如果代理类的class对象已经存在于类加载器的实现,则返回一份缓存中的拷贝,否则代理类的class对象将由ProxyClassFactory创建。

最开始,代理类的class对象一定不存在于proxyClassCache中,所以我们来看看ProxyClassFactory是如何生成代理类的class对象:

    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) {
                /*
                 * Verify that the class loader resolves the name of this
                 * interface to the same Class object.
                 */
                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");
                }
                /*
                 * Verify that the Class object actually represents an
                 * interface.
                 */
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 * Verify that this interface is not a duplicate.
                 */
                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;

            /*
             * 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 + ".";
            }

            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                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());
            }
        }
    }

  

如果从proxyClassCache中获取class对象,却发现class对象不存在,最终程序会执行到ProxyClassFactory.apply(ClassLoader loader, Class<?>[] interfaces)方法来创建一个class对象。ProxyClassFactory.apply方法中,会校验所传入的类加载器是否有权限加载类,构造代理对象的类名和访问标志,最终通过ProxyGenerator.generateProxyClass(final String var0, Class<?>[] var1, int var2)这个方法,生成一个字节数组,这个字节数组就是我们的class对象。于是,我们转而看一下ProxyGenerator.generateProxyClass这个方法:

public class ProxyGenerator {
	……
	private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
	……
    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) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if (var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                            Files.createDirectories(var3);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class");
                        }

                        Files.write(var2, var4, new OpenOption[0]);
                        return null;
                    } catch (IOException var4x) {
                        throw new InternalError("I/O exception saving generated file: " + var4x);
                    }
                }
            });
        }

        return var4;
    }
	……
}

  

这段代码中会生成一个类型为ProxyGenerator的变量var3,这个变量在调用generateClassFile()方法时,会根据我们传入的类名、实现接口和访问标志生成一个字节数组,要知道,一个class文件本身就是一个字节数组,在这个方法中,还会为我们重写代理类的的hashCode()、equals(Object obj)和toString()方法,因为篇幅的原因,这里就不再多介绍generateClassFile()方法。这里我们注意到上面有个环境变量:sun.misc.ProxyGenerator.saveGeneratedFiles,一般在运行期生成的代理类的class对象,在我们工程下面是不会有class文件的,但如果我们修改这个变量为true,会将运行期生成代理类的class文件。这里我们在Client.main(String[] args)函数的开头添加如下代码:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

 

重新执行Client的代码,可以看到在我们工程下面多出一个目录:com.sun.proxy,这个目录下面有个$Proxy0.class文件,就是我们的代理类的class文件,我们用idea反编译的结果看看对应的Java代码: 

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

package com.sun.proxy;

import com.leolin.jvm.bytecode.Subject;
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 Subject {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    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 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 request() throws  {
        try {
            super.h.invoke(this, m3, (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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.leolin.jvm.bytecode.Subject").getMethod("request");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

  

可以看到,代理类$Proxy0的构造参数要求传入一个InvocationHandler调用句柄的实例,然后再将InvocationHandler实例传给父类Proxy的构造方法。代理类中还有4个Method类型的静态变量,Method类型可以帮助我们在程序运行期间,执行某个对象的目标方法。在静态代码块中分别给这4个Method变量赋值,m1、m2、m3、m4分别用于执行equals、toString、request、hashCode方法,而代理类重写之前的四个方法,并将对应的Method变量传入到调用句柄的invoke方法,调用句柄最终会执行被代理对应对应的方法,如果被代理对象本身没有重写该方法,如:equals、toString和hashCode,则调用父类的方法。

posted @ 2020-05-21 21:56  北洛  阅读(208)  评论(0编辑  收藏  举报