• 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,则调用父类的方法。

  • 相关阅读:
    用 Python、 RabbitMQ 和 Nameko 实现微服务
    自定义Docker容器的 hostname
    ubuntu下升级R版本
    pair correlation ggpair ggmatrix
    RabbitMQ消息队列(一): Detailed Introduction 详细介绍
    ng-controller event data
    node项目换了环境node_modules各种报错
    Blast本地化
    angularjs $q、$http 处理多个异步请求
    解决angular页面值闪现问题
  • 原文地址:https://www.cnblogs.com/beiluowuzheng/p/12915459.html
Copyright © 2020-2023  润新知