zoukankan      html  css  js  c++  java
  • Java中的动态代理

    这几天打算研究一下Retrofit,遇到的第一个知识点就是动态代理 。动态代理对应设计模式中的代理模式,还有个模式叫做静态代理,我们知道代理类与目标类需要实现相同的接口,在静态代理中手动实现这些接口会产生大量的模板代码,动态代理就可以很好地解决这一问题。

    1、示例

    接下来的例子,我们使用动态代理为网络请求添加日志,首先需要定义请求接口。

    public interface HttpRequest {
        String request(String path);
    }  

    然后实现真正的网络请求类,这里直接返回字符串。

    public class HttpRequestImpl implements HttpRequest {
        @Override
        public String request(String path) {
            return "<html></html>";
        }
    }

    接着新建一个实现InvocationHandler接口的回调类,每个代理类实例都会与一个回调接口关联,所有对代理类接口中方法的调用都会被转发到该接口的 invoke 方法。这里我们的网络请求接口只有一个方法,如果代理类实现的接口有多个方法,还需要根据 invoke 方法的 method 参数判断当前正在调用哪一个方法,再执行相应的逻辑。

    public class HttpRequestProxy implements InvocationHandler {
    
        private Object mTarget;
    
        public Object bind(Object obj) {
            mTarget = obj;
            return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                    obj.getClass().getInterfaces(),
                    this);
        }
    
        @Override
        public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
            System.out.println("log ---> path: " + objects[0]);
            String result = (String) method.invoke(mTarget, objects);
            System.out.println("log ---> content: " + result);
            return result;
        }
    }  

    在使用时,我们只需要将目标对象绑定到回调接口中,然后将Proxy类的 newProxyInstance 方法返回的代理类实例转换为其实现的接口即可。

    public class Main {
    
        public static void main(String[] arg) {
            HttpRequestImpl impl = new HttpRequestImpl();
            HttpRequestProxy proxy = new HttpRequestProxy();
            HttpRequest request = (HttpRequest) proxy.bind(impl);
            request.request("localhost");
        }
        // 输出:
        // log ---> path: localhost
        // log ---> content: <html></html>
    }

    2、原理

    这一部分我们从源码角度分析动态代理的实现机制,先看一下之前示例里系统生成的代理类:

    public final class $Proxy0 extends Proxy implements HttpRequest {
        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})).booleanValue();
            } 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 String request(String var1) throws  {
            try {
                return (String)super.h.invoke(this, m3, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
                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.test.HttpRequest").getMethod("request", Class.forName("java.lang.String"));
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    

    生成的代理类先在静态代码块中初始化对应成员方法的Method对象,而所有成员方法都是调用与其实例关联的回调接口的 invoke 方法,并将自身对应的Method对象和参数传递给。除了我们接口中的函数外,系统在还代理类中帮我们复写了 equals toString hashCode 这三个Object类的方法。

    接下来以Proxy类的 newProxyInstance 方法为切入点,分析代理类的生成过程。

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 
        throws IllegalArgumentException {
        Class<?> cl = getProxyClass0(loader, interfaces);
        Constructor<?> cons = cl.getConstructor(constructorParams);
        return cons.newInstance(new Object[]{h});
    }  

    先是通过 getProxyClass0 方法获取到了代理类的Class对象,然后通过反射将我们的回调接口作为参数调用其构造函数。

    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        // 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);
    }  

    proxyClassCache 是一个WeakCache类型的变量,如果对应的代理类已创建就直接返回,否则其内部会调用ProxyClassFactory中的函数生成代理类。  

    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
        // 首先判断接口数组中是否与重复元素。
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) {
            Class<?> interfaceClass = null;
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
        }
        // 接下来确定代理类所在的包,如果所有接口都被public修饰,那么代理类的包由系统
        // 决定。如果如果存在非public型的接口,那么代理类需要与其在一个包下。如果有多个
        // 非public型接口并且所在的包不同则抛出异常。
        String proxyPkg = null; 
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
        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) proxyPkg = "";
        {
            // 获取接口中的所有方法,检查这些接口的方法中是否存在方法名与参数列表相同
            // 但返回值类型却不同的方法。在这个方法中系统首先将hascode, toString, equals
            // 这三个函数添加到集合中。
            List<Method> methods = getMethods(interfaces);
            Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE);
            validateReturnTypes(methods);
            // 如果接口中存在重复的方法,则按接口的排序仅保留第一个方法。
            List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods);
            Method[] methodsArray = methods.toArray(new Method[methods.size()]);
            Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]);
            long num = nextUniqueNumber.getAndIncrement();
            // 生成代理类名称,默认为:$Proxy0、$Proxy1、$Proxy2...
            String proxyName = proxyPkg + proxyClassNamePrefix + num;
            return generateProxy(proxyName, interfaces, loader, methodsArray,
                                 exceptionsArray);
        }
    }   

    最后代理类将在 generateProxy 方法中产生,这是一个native函数,它会在内存中拼凑出对应代理类class文件的数组,然后加载到虚拟机中。

  • 相关阅读:
    封装 lhgDialog弹出窗口组件 为C#的api
    最简单的dbhelper类
    asp.net无组件导出Excel
    js中的escape的用法汇总
    【Demo 0110】获取内存信息
    【Demo 0119】延时加载DLL 编程
    【Demo 0112】共享数据段
    【Demo 0116】堆的使用
    【Demo 0111】获取进程当前内存使用
    【Demo 0118】动态加载DLL
  • 原文地址:https://www.cnblogs.com/mmmmar/p/8670459.html
Copyright © 2011-2022 走看看