您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

一文带你弄懂 Java 动态代理

时间:2020-06-20 16:26:33  来源:  作者:
一文带你弄懂 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 ;

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.requireNon(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 = ;

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) != ) {

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 == ) {

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



Tags:Java 动态代理   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
作者 | mjzuo责编 | 王晓曼出品 | CSDN 博客在说动态代理之前,先来简单看下代理模式。代理是最基本的设计模式之一。它能够插入一个用来替代“实际”对象的“代理”对象,来提...【详细内容】
2020-06-20  Tags: Java 动态代理  点击:(45)  评论:(0)  加入收藏
▌简易百科推荐
面向对象的特征之一封装 面向对象的特征之二继承 方法重写(override/overWrite) 方法的重载(overload)和重写(override)的区别: 面向对象特征之三:多态 Instanceof关键字...【详细内容】
2021-12-28  顶顶架构师    Tags:面向对象   点击:(2)  评论:(0)  加入收藏
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  CF07    Tags:Java   点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Java架构师之路    Tags:JAVA   点击:(5)  评论:(0)  加入收藏
大家好!我是老码农,一个喜欢技术、爱分享的同学,从今天开始和大家持续分享JVM调优方面的经验。JVM调优是个大话题,涉及的知识点很庞大 Java内存模型 垃圾回收机制 各种工具使用 ...【详细内容】
2021-12-23  小码匠和老码农    Tags:JVM调优   点击:(11)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  dingle    Tags:JDBC   点击:(13)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  JAVA小白    Tags:Java   点击:(11)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  小西学JAVA    Tags:JAVA并发   点击:(11)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  唯一浩哥    Tags:Java基础   点击:(17)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  软件老王    Tags:logback   点击:(19)  评论:(0)  加入收藏
本篇文章我们以AtomicInteger为例子,主要讲解下CAS(Compare And Swap)功能是如何在AtomicInteger中使用的,以及提供CAS功能的Unsafe对象。我们先从一个例子开始吧。假设现在我们...【详细内容】
2021-12-14  小西学JAVA    Tags:JAVA   点击:(21)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条