一文带你弄懂 Java 动态代理 | 原力计划

一文带你弄懂 Java 动态代理 | 原力计划 一文带你弄懂 Java 动态代理 | 原力计划

作者 | mjzuo

责编 | 王晓曼

出品 | CSDN 博客

在说动态代理之前,先来简单看下代理模式。

代理是最基本的设计模式之一。它能够插入一个用来替代“实际”对象的“代理”对象,来提供额外的或不同的操作。这些操作通常涉及与“实际”对象的通信,因此“代理”对象通常充当着中间人的角色。

一文带你弄懂 Java 动态代理 | 原力计划

代理模式

代理对象为“实际”对象提供一个替身或占位符以控制对这个“实际”对象的访问。被代理的对象可以是远程的对象,创建开销大的对象或需要安全控制的对象。来看下类图

一文带你弄懂 Java 动态代理 | 原力计划

再来看下类图对应代码,这是 IObject 接口,真实对象 RealObj 和代理对象 ObjProxy 都实现此接口:

/**  * 为实际对象Tested和代理对象TestedProxy提供对外接口  */ public interface IObject {     void request(); }

RealObj 是实际处理 request() 逻辑的对象,但是出于设计的考量,需要对RealObj内部的方法调用进行控制访问。

public class RealObject implements IObject {     @Override     public void request() {         // 模拟一些操作     } } 

ObjProxy 是 RealObj 的代理类,其同样实现了 IObject 接口,所以具有相同的对外方法。客户端与 RealObj 的所有交互,都必须通过 ObjProxy。

public class ObjProxy implements IObject {     IObject realT;     public ObjProxy(IObject t) {         realT = t;     }     @Override     public void request() {         if (isAllow())             realT.request();     }     /**      * 模拟针对请求的校验判断      */     private boolean isAllow() {         return true;     } } 

一文带你弄懂 Java 动态代理 | 原力计划

番外

代理模式和装饰者模式不管是在类图,还是在代码实现上,几乎是一样的,但我们为何还要进行划分呢?其实学设计模式,不能拘泥于格式,不能死记形式,重要的是要理解模式背后的意图,意图只有一个,但实现的形式却可能多种多样。这也就是为何那么多变体依然属于xx设计模式的原因。

代理模式的意图是替代真正的对象以实现访问控制,而装饰者模式的意图是为对象加入额外的行为。

一文带你弄懂 Java 动态代理 | 原力计划

动态代理

Java 的动态代理可以动态的创建代理并动态的处理所代理方法的调用,在动态代理上所做的所以调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的策略。类图见下:

一文带你弄懂 Java 动态代理 | 原力计划

还以上面的代码为例,这是对外的接口 IObject:

public interface IObject {     void request(); }

这是 InvocationHandler 的实现类,类图中 Proxy 的方法调用都会被系统传入此类,即 invoke 方法,而 ObjProxyHandler 又持有着 RealObject 实例,所以 ObjProxyHandler 是“真正”对 RealObject 对象进行访问控制的代理类。

public class ObjProxyHandler implements InvocationHandler {     IObject realT;     public ObjProxyHandler(IObject t) {         realT = t;     }     @Override     public Object invoke(Object proxy, Method method, Object[] args)             throws Throwable {         // request方法时,进行校验         if (method.getName().equals("request") && !isAllow())             return null;         return method.invoke(realT, args);     }     /**      * 模拟针对请求的校验判断      */     private boolean isAllow() {         return false;     } }

RealObj 是实际处理 request() 逻辑的对象。

public class RealObject implements IObject {     @Override     public void request() {         // 模拟一些操作     } }

动态代理的使用方法如下:我们通过 Proxy.newProxyInstance 静态方法来创建代理,其参数如下,一个类加载器、一个代理实现的接口列表、一个 InvocationHandler 的接口实现。

    public void startTest() {         IObject proxy = (IObject) Proxy.newProxyInstance(                 IObject.class.getClassLoader(),                 new Class[]{IObject.class},                 new ObjProxyHandler(new RealObject()));         proxy.request(); // ObjProxyHandler的invoke方法会被调用     } 

一文带你弄懂 Java 动态代理 | 原力计划

Proxy源码

来看下Proxy 源码,当我们 newProxyInstance(…) 时,首先系统会进行判空处理,之后获取我们实际的 Proxy 代理类 Class 对象,再通过一个参数的构造方法生成我们的代理对象 p(p : 返回值),这里能看出来 p 是持有我们的对象 h 的。注意 cons.setAccessible(true) 表示,即使是 cl 是私有构造,也可以获得对象。源码见下:

public static Object newProxyInstance(ClassLoader loader,                                           Class<?>[] interfaces,                                           InvocationHandler h)         throws IllegalArgumentException     {         Objects.requireNonNull(h);         final Class<?>[] intfs = interfaces.clone();         /*          * Look up or generate the designated proxy class.          */         Class<?> cl = getProxyClass0(loader, intfs);         ...         final Constructor<?> cons = cl.getConstructor(constructorParams);             final InvocationHandler ih = h;             if (!Modifier.isPublic(cl.getModifiers())) {                 cons.setAccessible(true);                 // END Android-removed: Excluded AccessController.doPrivileged call.             }             return cons.newInstance(new Object[]{h});         ...     }

其中 getProxyClass0(…) 是用来检查并获取实际代理对象的。首先会有一个65535的接口限制检测,随后从代理缓存 ProxyClassCache 中获取代理类,如果给定的接口不存在,则通过 ProxyClassFactory 新建。见下:

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

存放代理 Proxy.class 的缓存 proxyClassCache,是一个静态常量,所以在我们类加载时,其就已经被初始化完毕了。见下:

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>         proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

Proxy 提供的getInvocationHandler(Object proxy)方法和 invoke(…) 方法很重要。分别为获取当前代理关联的调用处理器对象 InvocationHandler,并将当前Proxy方法调用调度给 InvocationHandler。

是不是与上面的代理思维很像,至于这两个方法何时被调用的,推测是写在了本地方法内,当我们调用 proxy.request 方法时(系统创建Proxy时,会自动 implements 用户传递的接口,可以为多个),系统就会调用 Proxy invoke 方法,随后 proxy 将方法调用传递给 InvocationHandler。

public static InvocationHandler getInvocationHandler(Object proxy)         throws IllegalArgumentException     {         /*          * Verify that the object is actually a proxy instance.          */         if (!isProxyClass(proxy.getClass())) {             throw new IllegalArgumentException("not a proxy instance");         }         final Proxy p = (Proxy) proxy;         final InvocationHandler ih = p.h;         return ih;     }     // Android-added: Helper method invoke(Proxy, Method, Object[]) for ART native code.     private static Object invoke(Proxy proxy, Method method, Object[] args) throws Throwable {         InvocationHandler h = proxy.h;         return h.invoke(proxy, method, args);     }

一文带你弄懂 Java 动态代理 | 原力计划

ProxyClassFactory

重点是ProxyClassFactory 类,这里的逻辑不少,所以我将ProxyClassFactory 单独抽出来了。能看到,首先其会检测当前 interface 是否已被当前类加载器所加载。

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

之后会进行判断是否为接口。这也是我们说的第二个参数为何不能传基类或抽象类的原因。

                if (!interfaceClass.isInterface()) {                     throw new IllegalArgumentException(                         interfaceClass.getName() + " is not an interface");                 } 

之后判断当前 interface 是否已经存在于缓存cache内了。

                    if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {                     throw new IllegalArgumentException(                         "repeated interface: " + interfaceClass.getName());                 }

检测非 public 修饰符的 interface 是否在是同一个包名,如果不是则抛出异常:

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

检验通过后,会 getMethods(…) 获取接口内的全部方法。

随后会对 methords 进行一个排序。具体的代码我就不贴了,排序规则是:如果方法相等(返回值和方法签名一样)或同是一个接口内方法,则当前顺序不变,如果两个方法所在的接口存在继承关系,则父类在前,子类在后。

之后 validateReturnTypes(…) 判断 methords 是否存在方法签名相同并且返回值类型也相同的methord,如果有则抛出异常。

接着通过 deduplicateAndGetExceptions(…) 方法,将 methords方法内的相同方法的父类方法剔除掉,并将 methord 保存在数组中。

转成一维数组和二维数组,Method[] methodsArray,Class< ? >[][] exceptionsArray,随后给当前代理类命名:包名 + “$Proxy” + num。

最后调用系统提供的 native 方法 generateProxy(…) 。这是真正的代理类创建方法。感兴趣的可以查看下java_lang_reflect_Proxy.cc源码和 class_linker.cc源码:

                 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()][]);                 /*                  * Choose a name for the proxy class to generate.                  */                 long num = nextUniqueNumber.getAndIncrement();                 String proxyName = proxyPkg + proxyClassNamePrefix + num;                 return generateProxy(proxyName, interfaces, loader, methodsArray,                                      exceptionsArray);

参考: Head First 设计模式: 中国电力出版社

版权声明:本文为CSDN博主「mjzuo」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/mingjiezuo/article/details/105930334

【END】

一文带你弄懂 Java 动态代理 | 原力计划

更多精彩推荐

更多精彩推荐

MATLAB 被禁点燃导火索,国产软件路在何方?

天猫京东618下单金额近万亿;苹果或选择印度组装新款iPhone SE;Adobe将于12月31日终止支持Flash| 极客头条

前字节跳动程序员 28 岁提前退休引热议,网友:我也想!

用 Python 训练自己的语音识别系统,这波操作稳了!

区块链如何解决食品安全问题?

你点的每个“在看”,我都认真当成了喜欢

原文 

http://mp.weixin.qq.com/s?__biz=MjM5MjAwODM4MA==&mid=2650754652&idx=3&sn=6c9f101c13d8aa3309ff7d776ee29f24

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 一文带你弄懂 Java 动态代理 | 原力计划

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址