美文网首页
深入理解Java动态代理

深入理解Java动态代理

作者: JimmieYang | 来源:发表于2019-02-02 11:26 被阅读1379次

    代理模式

    代理模式UML

    使用代理模式创建代理对象,让代理对象来控制对某个对象的访问, 被代理对象可以是远程对象,创建开销大的对象或者需要安全控制的对象等.

    Proxy 称为代理对象.
    RealSubject 是被代理的对象,也称为委托对象.
    Subject 是他们抽象出来的接口.

    RealSubjectProxy都继承自Subject, Proxy 内部持有一个 RealSubject的变量,调用代理的方法,代理中将直接调用RealSubject对应的方法.

    静态代理

    静态代理,在编译期间就需要指定好代理类,即在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了.

        interface Subject {
            void request();
        }
    
         class Proxy implements Subject {
            private RealSubject realSubject;
    
            public Proxy(RealSubject realSubject) {
                this.realSubject = realSubject;
            }
    
            @Override
            public void request() {
                // 添加log
                System.out.println("log...");
                realSubject.request();
            }
        }
    
         class RealSubject implements Subject {
    
            @Override
            public void request() {
                // todo ...
                System.out.println("request from http");
            }
        }
    
        public static void main(String[] args) {
            Subject proxy = new Proxy(new RealSubject());
            proxy.request();
        }
    

    这样做的优点:

    1. 可以隐藏委托类的实现,可以进行权限控制和安全控制.

    2. 实现客户端和委托类解耦,只要对外接口不变,客户端就不需要修改调用方式.

    3. 通过扩展代理类,进行一些功能的附加与增强.

    动态代理

    动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码.代理类和委托类的关系是在程序运行时确定。

    相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。

    Java中的动态代理是使用Proxy.newProxyInstance()方法生成的.

    先观察下 Proxy.newProxyInstance()的参数.

        Object newProxyInstance(
                ClassLoader loader,
                Class<?>[] interfaces,
                InvocationHandler h
        )
    

    ClassLoader loader:

    表示加载生成代理类的类加载器,通常情况下是直接使用
    当前线程的类加载器 Thread.currentThread().getContextClassLoader(),
    或者 使用 代理接口的类加载器. 如 Subject.class.getClassLoader().

    少部分情况下,需要使用特殊的类加载器,如Android插件化中,使用动态代理,可能需要传入插件对应的类加载器.

    Class<?>[] interfaces:

    这里,传入你感兴趣的 委托类所实现的接口.
    如下面例子中的 Subject.class, 则你需要传入 new Class[]{Subject.class}作为参数.
    如果 RealSubject实现了多个接口Subject1,Subject2,Subject3...,而你对其中Subject1,Subject2感兴趣,
    你可以传入 new Class[]{Subject1.class,Subject2.class}, JVM运行时生成的字节码类,将会实现传入的这些接口.

    InvocationHandler h

    方法调度处理器接口. 内部有一个回调方法 Object invoke(Object proxy, Method method, Object[] args).
    实现该方法,可以拦截 上一个参数传入的接口的方法, 你可以对这些方法进行增强, 甚至改变方法的行为.

    实现 InvocationHandler 通常需要传入一个 委托对象, 然后在invoke()方法中,对委托对象进行修改或者增强操作.

    来看一个简单的动态代理的例子.

    // proxy.Subject.java
    public interface Subject {
        void doSomething();
    }
    
    // proxy.RealSubject.java
    public class RealSubject implements Subject {
        @Override
        public void doSomething() {
            System.out.println("RealSubject do ...");
        }
    }
    
    // ReflectTest.java
    public class ReflectTest {
        public static void main(String[] args) {
            // 委托对象
            RealSubject realSubject = new RealSubject();
            // 方法调度处理器
            InvocationHandler handler = new ProxyHandler(realSubject);
    
            // 生成代理对象
            Subject proxy = (Subject) Proxy.newProxyInstance(
                    Thread.currentThread().getContextClassLoader(),
                    new Class[]{Subject.class},
                    handler
            );
    
            // 这里使用辅助类,保存JVM生成动态代理类到本地,此时先忽略
            ProxyUtils.generateClassFile(realSubject.getClass(), "SubjectProxy");
    
            // 代理运行方法
            proxy.doSomething();
        }
    
        // 代理方法调度器
        static class ProxyHandler implements InvocationHandler {
            // 委托对象
            RealSubject realSubject;
    
            ProxyHandler(RealSubject realSubject) {
                this.realSubject = realSubject;
            }
    
            /**
             * 在此方法中,进行委托对象方法的增强或者修改
             * 该示例中只是简单的对其添加log.
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("do before ...");
    
                // 处理委托对象的方法
                Object result = method.invoke(realSubject, args);
                System.out.println("do after ...");
                return result;
            }
        }
    }
    
    //do before ...
    //RealSubject do ...
    //do after ...
    

    从这里,我们可以窥探出 动态代理与静态代理的一些差别,我们来总结一下.

    动态代理使用 InvocationHandlerinvoke()方法来统一处理委托类的方法, 也就是说, 即使委托对象实现的接口中有几百个方法,我们也只要在这一个方法中处理即可.换句话说,如果委托类增加了一些方法,而我们不需要对方法进行修改,那动态代理这部分的代码,可以不需要改动,静态代理则达不到这种效果.

    动态代理在运行时,动态生成字节码数据,我们在编写代码的时候是看不到真正的代理类代码.

    动态代理一个重要的应用就是 AOP(面向切面编程), 主要处理 日志记录,性能统计,安全控制,事务处理,异常处理等等

    如果我们需要对一些 不对外开发的或者不容易直接操作的类或者api进行操作,我们也可以使用 动态代理来处理. 如Android插件化中,对系统资源的HOOK, 如对ActivityManager的Hook就用到动态代理技术.

    动态代理原理

    Java的动态代理是通过 Proxy .newProxyInstance()方法来生成的. 我们来追踪下源码, 源码中 我把无关和不打紧的代码去掉,以便分析.

    public class Proxy {
        // 生成的动态代理的构造参数类
        private static final Class<?>[] constructorParams = {InvocationHandler.class};
    
        // 动态代理类的缓存池
        private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
                proxyClassCache = new WeakCache<>(new KeyFactory(), new Proxy.ProxyClassFactory());
    
        public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
                throws IllegalArgumentException {
            final Class<?>[] intfs = interfaces.clone();
            // 1. 生成动态代理类
            Class<?> cl = getProxyClass0(loader, intfs);
            try {
                // 2. 反射创建动态代理类实例
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                return cons.newInstance(new Object[]{h});
            } catch (Exception e) {
                throw new InternalError(e.toString(), e);
            }
        }
    
        private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
            // 如果通过传入的 类加载器和接口类已经缓存过, 则直接从缓存中获取 之前已经生成的代理类
            // 否则, 将会通过ProxyClassFactory来创建一份动态代理类
            return proxyClassCache.get(loader, interfaces);
        }
    
        /**
         * 生成代理类的工厂
         */
        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) {
    
                    // 1. 验证传入的接口类,是否是用传入的 类加载器 加载的,不是则报错
                    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");
                    }
    
                    // 2. 验证传入的类是接口类型, 不是则报错
                    if (!interfaceClass.isInterface()) {
                        throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");
                    }
    
                    // 3. 验证是否传入重复的接口类 , 是则报错
                    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;
    
                // 4. 对所有非公开的接口进行判断,
                // 4.1 判断所有的非公开接口,是不是在同一个包下, 不在 则报错, 在则生成的代理类与其 同包名
                // 4.2 如果没有非公开的接口,直接使用默认的包名 : com.sun.proxy
                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 + ".";
                }
    
                // 5. 生成动态代理类的名称
                // 形如 : com.sun.proxy.$Proxy0 com.sun.proxy.$Proxy1
                long   num       = nextUniqueNumber.getAndIncrement();
                String proxyName = proxyPkg + proxyClassNamePrefix + num;
    
                // 6. 通过代理生成器 直接生成 代理类的 字节码数据 即 .class类型的数据
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                        proxyName, interfaces, accessFlags);
                try {
                    // 7. 将字节码数据转为 Class 类
                    return defineClass0(loader, proxyName,
                            proxyClassFile, 0, proxyClassFile.length);
                } catch (ClassFormatError e) {
                    throw new IllegalArgumentException(e.toString());
                }
            }
        }
    }
    

    从源码中我们可以看出, 生成代理对象大步骤分为两个.

    1. 生成动态代理类对象
    2. 反射生成动态代理实例对象

    我们主要看第一个步骤. 它又分为好几个小的流程, 在上面源码分析中已经很清楚的写出来,这里就不在赘述.

    它最终会调用 ProxyGenerator.generateProxyClass() 方法来生成字节码文件.

    然而这个过程 我们并不能拿到 这个生成的对象,也就是说 我们从代码上是看不到这份文件的, 这对我们分析有很大的麻烦.

    而既然 代码是通过 ProxyGenerator.generateProxyClass()这个方法来生成, 我们可以尝试通过这个方法来将生成的代码保存到本地, 以便于分析.

    public class ProxyUtils {
        /**
         * 根据类信息和提供的代理类名称,生成字节码并且保存到本地
         *
         * @param clazz     委托类
         * @param proxyName 生成代理类的名称
         */
        public static void generateClassFile(Class<?> clazz, String proxyName) {
            // 根据类信息和提供的代理类名称,生成字节码
            byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
            String path      = clazz.getResource(".").getPath();
            System.out.println(path);
            FileOutputStream out = null;
    
            try {
                // 生成.class文件保存到本地
                out = new FileOutputStream(path + proxyName + ".class");
                out.write(classFile);
                out.flush();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (out != null) out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    这个类可以帮助我们保存生成的代理类对象, 在上个例子中 有调用到过 ProxyUtils.generateClassFile(realSubject.getClass(), "SubjectProxy");,
    我们直接打开生成的类来看下.

    public final class SubjectProxy extends Proxy implements Subject {
        private static Method m1;
        private static Method m3;
        private static Method m2;
        private static Method m0;
    
        public SubjectProxy(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 doSomething() throws  {
            try {
                super.h.invoke(this, m3, (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);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m3 = Class.forName("proxy.Subject").getMethod("doSomething");
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    

    可以看出, 生成的代理类, 是继承 Proxy类,并且实现了Subject接口, 构造函数传入 InvocationHandler对象.
    然后,它将实现来的所有方法, 都通过反射的方式,调用 InvocationHandler.invoke()方法来实现.
    InvocationHandler中将传入委托类 来完成反射调用.

    也就是说, 代理类, 最终调用的是 通过InvocationHandler.invoke()方法进行增强或者修改的, 委托类(RealSubject)所对应的方法.

    至此,我们分析完了, 动态代理实现的原理.

    动态代理的不足

    动态代理是一定要基于接口的, 如果委托对象没有实现相应的接口, 是无法对其创建动态代理的.
    JDK为我们提供的代理实现方案确实没法解决这个问题, 那怎么办呢? 可以使用 CGLib动态代理, 这里就不对其进行展开, 感兴趣的可以自行搜索了解.

    相关文章

      网友评论

          本文标题:深入理解Java动态代理

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