美文网首页
JDK动态代理

JDK动态代理

作者: 因你而在_caiyq | 来源:发表于2019-04-12 08:33 被阅读0次

    原创文章,转载请注明原文章地址,谢谢!

    动态代理类是在程序运行期间确定的,由JVM通过反射机制动态生成的,代理对象和真实对象的关系是在程序运行时期才确定的。

    JDK动态代理API
    • java.lang.reflect.Proxy 类
      这是Java动态代理机制生成的代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

    方法:
    static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler hanlder)
    参数:
    loader:类加载器
    interfaces:代理的接口
    hanlder:代理执行处理器
    返回:
    动态生成的代理对象

    • java.lang.reflect.InvocationHandler 接口
      该接口负责集中处理动态代理类上的所有方法调用。

    方法:
    Object invoke(Object proxy, Method method, Object[] args)
    参数:
    proxy:代理对象
    method:被代理的方法
    args:被代理方法的入参
    返回:
    真实方法的返回结果

    JDK动态代理的实现

    我们不再需要创建代理类,因为在动态代理中,代理对象是在运行时期自己生成的,但是也不是无缘无故生成吧,我们总要做一些处理。根据上面的API,我们创建一个处理类,比如就叫事务处理类,实现InvocationHandler接口,另外给Proxy类提供ClassLoader对象和代理接口类型数组,创建动态代理对象,最后是在处理器中实现动态增强。

    public class DynamicProxyHandler implements InvocationHandler {
    
        private EmployeeService employee;
    
        private TransactionManager transactionManager = new TransactionManager();
    
        public DynamicProxyHandler(EmployeeService employee) {
            this.employee = employee;
        }
    
        /**
         * 获取代理对象
         *
         * @return 代理对象
         */
        public Object getProxyObject() {
            //参数1:类加载器
            //参数2:指定该代理对象需要实现的接口
            //参数3:当前处理对象
            return Proxy.newProxyInstance(this.getClass().getClassLoader(), employee.getClass().getInterfaces(), this);
        }
    
        /**
         * 处理方法
         *
         * @param proxy
         * @param method 目标对象中的方法
         * @param args
         * @return
         * @throws Throwable
         */
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                transactionManager.begin();
                method.invoke(employeeService);
                transactionManager.commit();
            } catch (Exception e) {
                e.printStackTrace();
                transactionManager.rollback();
            }
            return null;
        }
    }
    

    测试

    public static void main(String[] args) {
        //用于保存生成的代理类的字节码文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        EmployeeService employee = new EmployeeServiceImpl();
        DynamicProxyHandler handler = new DynamicProxyHandler(employee);
        EmployeeService proxy = (EmployeeService) handler.getProxyObject();
        proxy.save();
    }
    

    测试结果为

    开启事务
    调用DAO中的方法执行保存
    提交事务
    

    通过测试结果可以看出,同样在执行保存方法的时候,加入了事务处理的方法。那么接下来,我们就来详细分析下,为什么动态代理这样做,就能做到代理真实对象的方法呢?生成的代理类的字节码到底是什么样子?如何生成代理类的字节码呢?带着心中的种种疑惑,走进源码,一探究竟!

    源码分析

    在看源码之前,因为在前面测试的时候,加了一行保存字节码的代码。

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

    所以我们先来看看这个生成的文件。它在com.sun.proxy包下。

    public final class $Proxy0 extends Proxy implements EmployeeService {
        private static Method m1;
        private static Method m4;
        private static Method m2;
        private static Method m0;
        private static Method m3;
    
        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 save() throws  {
            try {
                super.h.invoke(this, m4, (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);
            }
        }
    
        public final void update() throws  {
            try {
                super.h.invoke(this, m3, (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"));
                m4 = Class.forName("com.alibaba.proxy.jdk.EmployeeService").getMethod("save");
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
                m3 = Class.forName("com.alibaba.proxy.jdk.EmployeeService").getMethod("update");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    

    简单介绍下,首先这个并不是原始的二进制的字节码文件,在IDEA中打开的时候,已经是反编译后的了。这里面包含的所有的方法信息,构造器,入参,返回,以及异常等等。所以细想一下,如果jdk要生成这个文件,是不是只要收集这些所有的信息,然后组装起来不就可以了?

    接下来我们来分析下源码。首先需要找到一个入口,那就是我们创建代理类的地方,主方法。

    public static void main(String[] args) {
        //用于保存生成的代理类的字节码文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        EmployeeService employee = new EmployeeServiceImpl();
        DynamicProxyHandler handler = new DynamicProxyHandler(employee);
        EmployeeService proxy = (EmployeeService) handler.getProxyObject();
        //增加这一行代码,看看这个proxy到底是什么?
        System.out.println(proxy.getClass());
        proxy.save();
    }
    

    我们疑惑的地方在于,在执行 handler.getProxyObject()以后,就得到了一个EmployeeService的对象,从而才能去执行下面的代码,那么这个获取到的proxy对象是什么类型的呢?在上面的代码中,增加了一行输出打印,看看这个proxy到底是什么?
    结果是:

    class com.sun.proxy.$Proxy0
    

    这是一个在class com.sun.proxy包下的代理类,且类名是$Proxy0,那这个又是什么鬼呢?接下来继续往下看。

        public Object getProxyObject() {
            //参数1:类加载器
            //参数2:指定该代理对象需要实现的接口
            //参数3:当前处理对象
            return Proxy.newProxyInstance(this.getClass().getClassLoader(), employee.getClass().getInterfaces(), this);
        }
    

    创建代理对象实际上是这里面的Proxy类来完成的,调用其自身的一个静态方法newProxyInstance。然后我们走进去。(保留了源码上面的英文注释,将对应的中文注释标在下面)

        @CallerSensitive
        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);
                }
                //获取代理类Class的构造器
                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);
            }
        }
    

    在上述源码中标注的中文注释,都是比较重要的。大概的思路就是,根据核心方法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);
        }
    

    这个方法里面内容还是不多的,先是判断了需要代理的接口数有没有超过一定数量,然后最终调用了下面的get方法。这里需要知道,proxyClassCache是一个缓存,它是从缓存中获取需要的代理对象,而当缓存中没有的时候,是通过ProxyClassFactory这个工厂类来创建的,所以找到这个类,看看它里面的流程。

        /**
         * A factory function that generates, defines and returns the proxy class given
         * the ClassLoader and array of interfaces.
         */
        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();
            
            //apply方法是生成Class类实例的方法
            @Override
            public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
                //一个Map集合,用来存放所有用于代理生成的Class实例
                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.
                 */
                //public接口和非public接口的处理是不一样的。非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) {
                    // if no non-public proxy interfaces, use com.sun.proxy package
                    //public接口生成的Class所在的包就是com.sun.proxy
                    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.
                 * 生成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());
                }
            }
        }
    

    ProxyClassFactory是Proxy的一个静态的内部类,接下来我们来细细品味一下其内部的代理。

    // 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();
    

    还记得这个么?是否有些眼熟呢?没错,我们在测试的时候,生成的代理类的字节码的时候,字节码文件的名字叫$Proxy0,那么在这里,proxyClassNamePrefix是叫前缀,nextUniqueNumber是一个计数器,是不是就是那个数字0呢?好,接下来继续往下。
    真正生成Class类实例的是apply方法。首先会创建一个Map,遍历interfaces,因为代理的实现的接口是一个数组,并不是一个,所以这里遍历处理。

    public static final String PROXY_PACKAGE = "com.sun.proxy";
    if (proxyPkg == null) {
        // if no non-public proxy interfaces, use com.sun.proxy package
        proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
    }
    long num = nextUniqueNumber.getAndIncrement();
    String proxyName = proxyPkg + proxyClassNamePrefix + num;
    

    看到这里就会明白,果然其生成的字节码的名字是通过这样的逻辑规则生成的,也验证了我们前面的猜想。接下来这一段中的方法,就是生成自己码文件的核心方法:generateProxyClass。

    /*
     * Generate the specified proxy class.
     */
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
    try {
        return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
    } catch (ClassFormatError e) {
        throw new IllegalArgumentException(e.toString());
    }
    

    在generateProxyClass方法中又调用了另一个方法:generateClassFile。

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

    创建字节码文件的方法:generateClassFile。它是按照二进制字节码文件的规则来生成的,可以参考jdk官网关于字节码的介绍,链接地址在文末。

    private byte[] generateClassFile() {
    
        /* ============================================================
         * Step 1: Assemble ProxyMethod objects for all methods to
         * generate proxy dispatching code for.
         */
    
        /*
         * Record that proxy methods are needed for the hashCode, equals,
         * and toString methods of java.lang.Object.  This is done before
         * the methods from the proxy interfaces so that the methods from
         * java.lang.Object take precedence over duplicate methods in the
         * proxy interfaces.
         */
        addProxyMethod(hashCodeMethod, Object.class);
        addProxyMethod(equalsMethod, Object.class);
        addProxyMethod(toStringMethod, Object.class);
    
        /*
         * Now record all of the methods from the proxy interfaces, giving
         * earlier interfaces precedence over later ones with duplicate
         * methods.
         */
        for (Class<?> intf : interfaces) {
            for (Method m : intf.getMethods()) {
                addProxyMethod(m, intf);
            }
        }
    
        /*
         * For each set of proxy methods with the same signature,
         * verify that the methods' return types are compatible.
         */
        for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
            checkReturnTypes(sigmethods);
        }
    
        /* ============================================================
         * Step 2: Assemble FieldInfo and MethodInfo structs for all of
         * fields and methods in the class we are generating.
         */
        try {
            methods.add(generateConstructor());
    
            for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
                for (ProxyMethod pm : sigmethods) {
    
                    // add static field for method's Method object
                    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());
                }
            }
    
            methods.add(generateStaticInitializer());
    
        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception", e);
        }
    
        if (methods.size() > 65535) {
            throw new IllegalArgumentException("method limit exceeded");
        }
        if (fields.size() > 65535) {
            throw new IllegalArgumentException("field limit exceeded");
        }
    
        /* ============================================================
         * Step 3: Write the final class file.
         */
    
        /*
         * Make sure that constant pool indexes are reserved for the
         * following items before starting to write the final class file.
         */
        cp.getClass(dotToSlash(className));
        cp.getClass(superclassName);
        for (Class<?> intf: interfaces) {
            cp.getClass(dotToSlash(intf.getName()));
        }
    
        /*
         * Disallow new constant pool additions beyond this point, since
         * we are about to write the final constant pool table.
         */
        cp.setReadOnly();
    
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        DataOutputStream dout = new DataOutputStream(bout);
    
        try {
            /*
             * Write all the items of the "ClassFile" structure.
             * See JVMS section 4.1.
             */
            // u4 magic;
            dout.writeInt(0xCAFEBABE);
            // u2 minor_version;
            dout.writeShort(CLASSFILE_MINOR_VERSION);
            // u2 major_version;
            dout.writeShort(CLASSFILE_MAJOR_VERSION);
    
            cp.write(dout);             // (write constant pool)
    
            // u2 access_flags;
            dout.writeShort(accessFlags);
            // u2 this_class;
            dout.writeShort(cp.getClass(dotToSlash(className)));
            // u2 super_class;
            dout.writeShort(cp.getClass(superclassName));
    
            // u2 interfaces_count;
            dout.writeShort(interfaces.length);
            // u2 interfaces[interfaces_count];
            for (Class<?> intf : interfaces) {
                dout.writeShort(cp.getClass(
                        dotToSlash(intf.getName())));
            }
    
            // u2 fields_count;
            dout.writeShort(fields.size());
            // field_info fields[fields_count];
            for (FieldInfo f : fields) {
                f.write(dout);
            }
    
            // u2 methods_count;
            dout.writeShort(methods.size());
            // method_info methods[methods_count];
            for (MethodInfo m : methods) {
                m.write(dout);
            }
    
            // u2 attributes_count;
            dout.writeShort(0); // (no ClassFile attributes for proxy classes)
    
        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception", e);
        }
    
        return bout.toByteArray();
    }
    

    至此,这就是jdk动态代理的实现过程。总结一下,重点就是生成代理对象,而这个代理对象怎么创建的?是通过代理对象的Class实例,利用Java中的反射获取到构造器,然后通过构造器来创建实例。而上面源码分析的过程,就是如果获取这个字节码以及创建Class实例的过程。

    字节码规范:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
    openjdk源码下载:https://download.java.net/openjdk/jdk8

    博客内容仅供自已学习以及学习过程的记录,如有侵权,请联系我删除,谢谢!

    相关文章

      网友评论

          本文标题:JDK动态代理

          本文链接:https://www.haomeiwen.com/subject/bnyfwqtx.html