点击关注公众号,Java干货及时送达

代理模式的目的是在不修改原有类方法设计的基础上,对方法行为进行增强。

为了好理解,举个实际场景,我们业务场景中经常有限流的需求,常规操作是在需要限流的接口代码前加入调用次数判断的代码,但是这样每个需要限流的方法都需要加,工作量大不说,一方面不好维护,不能很清晰的知道每个接口限流值,另一方面,限流代码和业务代码堆叠在一起,也影响代码阅读。解法是做一套统一限流,一般好点的会有专门的接口限流平台,配置对应的接口名,设置限流值,直接就可以限流,实现方式就可以用动态代理。不修改原接口的实现,对接口进行增强。

动态代理的优势是实现无侵入式的代码扩展,做方法的增强;让你可以在不用修改源码的情况下,增强一些方法;在方法的前后你可以做你任何想做的事情(甚至不去执行这个方法就可以)。

静态代理

既然有动态,那一定有静态,说下区别吧,

静态:最大的区别是静态是编译期就决定了,在程序运行之前,代理类的.class文件已经存在了。被代理类是什么,代理类实现方式。

举个栗子:

我现在有个接口,是把Json字符串解析成Object 对象,接口如下:

public interface IProvider {Object getData(String json);}

接口的实现类如下:

public class SimpleProvider implements IProvider {@Overridepublic Object getData(String json) {//解析json 拿到数据return parseJson(json);}

那现在有个需求,需要对 getData 方法做限流,指定用静态代理的方式。

需要很简单,我就直接贴了:

public class ProviderProxy implements IProvider{//持有一个被代理对象的引用(在这里是SimpleProvider)IProvider iProvider;public StaticProviderProxy(IProvider iProvider){this.iProvider = iProvider;}@Overridepublic Object getData(String json) {//做限流检查if(callSpeed > flowLimt) {//流量超限throw FlowLimitException();}Object object = iProvider.getData(json);return object;}
}
//main
public static void main(String[] args) {IProvider provider = new ProviderProxy(new SimpleProvider());provider.getData("{\"data\":{}}");
}

这就是静态代理,代理类(ProviderProxy)实现和需要做方法增强的被代理类(SimpleProvider)实现同一个接口(IProvider),方法具体实现上做增强,这里是限流检查。

动态代理

Java 动态代理

  • 动态代理类:在程序运行时,通过反射机制动态生成。

  • 动态代理类通常代理接口下的所有类。静态一般指定某个类代理。

  • 动态代理事先不知道要代理的是什么,只有在运行的时候才能确定。静态是编译期确定的。

还是以IProvider 接口为例,同样是要对 SimpleProvider 做增强,如下:

public class ProviderHandler implements InvocationHandler {Object target;public Object bind(Object target){this.target = target;//这里生成了代理对象return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//限流flowLimit(args);Object obj = method.invoke(target, args);//打印日志logger.info("print log...");return obj;}
}
//main
public static void main(String[] args) {ProviderHandler providerHandler = new ProviderHandler();IProvider iProvider = (IProvider) providerHandler.bind(new SimpleProvider());iProvider.getData("weibo.data");
}

这里有三个对象:

  1. SimpleProvider 对象 , 我们称之为被代理对象

  2. ProviderHandler 对象,我们称之为执行者对象

  3. Proxy对象 (通过在ProviderHandler bind方法中使用Proxy.newProxyInstance生成的对象) 我们称之为代理对象

这三个对象是什么关系呢?

Proxy是真正的代理类,SimpleProvider是被代理类,ProviderHandler是执行方法增强的执行者。

我们是为了增强SimpleProvider (被代理对象)的getData方法,就Proxy对象来代理被代理对象的执行,Proxy不亲自来做这件事,而是交给执行者对象ProviderHandler 来实现增加的目录,执行调用前的限流校验。

实际怎么实现的呢?

newProxyInstance源码

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{//对 Invocationhandler做判空处理Objects.requireNonNull(h);//复制[IProvider接口]final Class<?>[] intfs = interfaces.clone();//根据IProvider的类加载器IProvider接口生成了Proxy类,关键:根据类加载器和接口对象在JVM缓存中生成一个类对象Class<?> cl = getProxyClass0(loader, intfs);//获取构造器final Constructor<?> cons = cl.getConstructor(constructorParams);//保存InvocationHandler的引用final InvocationHandler ih = h;//通过构造器实例化Proxy代理对象return cons.newInstance(new Object[]{h});}

代码注释写的很清晰。

可能这个地方大家都会疑惑,生成的Proxy对象是怎样调用执行者的invoke函数的。

这个地方通过这段代码将Proxy0的class字节码输出到文件。

byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", WeiboProvider.class.getInterfaces());
String path = "C:**/IdeaProjects/study/out/production/study/SimpleProxy.class";
try(FileOutputStream fos = new FileOutputStream(path)) {fos.write(classFile);fos.flush();System.out.println("代理类class文件写入成功");} catch (Exception e) {System.out.println("写文件错误");}

反编译Proxy0如下:

//Proxy0 是动态生成的类,继承自Proxy,实现了IProvider接口
public final class $Proxy0 extends Proxy implements IProvider {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 getData(String var1) throws  {try {//m3就是IProvider 接口的getData方法 //super.h 是父类java.lang.reflect.Proxy的属性 InvocationHandlerreturn (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 {m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);//m3就是IProvider 接口的getData方法m3 = Class.forName("aop.IProvider").getMethod("getData", new Class[]{Class.forName("java.lang.String")});m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

重点在 return (String)super.h.invoke(this, m3, new Object[]{var1});代码。

$Proxy0继承Proxy类,实现了IProvider接口,所以也有getData()函数,而getData函数调用的是执行者InvocationHandler的invoke方法,m3是通过反射拿到的Method对象,所以看getData调用invoke传递的。三个参数,第一个是Proxy对象,第二个是getData方法对象,第三个是参数。

总结一下:

  • 动态代理的本质就是,生成一个继承自Proxy,实现被代理接口(IProvider)的类 - Proxy0。

  • Proxy0 持有InvocationHandler实例,InvocationHandler 持有SimpleProvider实例。Proxy0调用接口 getData方法时,先传递给InvocationHandler,InvocationHandler再传递给SimpleProvider实例。

动态代理实际上就是帮我们在JVM内存中直接重新生成了代理类class和对应类对象,然后通过执行者InvocationHandler调用被代理对象SimpleProvider。

Spring AOP中的代理

Spring代理其实是对JDK动态代理和CGLIB代理进行了封装,并且引入了AOP的概念,同时引入了AspectJ中的一些注解:@pointCut @After 等。

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");}// 如果是接口,使用jdk代理 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {return new JdkDynamicAopProxy(config);}//否则使用cglibreturn new ObjenesisCglibAopProxy(config);}else {return new JdkDynamicAopProxy(config);}}
热门内容:Redis分布式锁使用不当,酿成一个重大事故,超卖了100瓶飞天茅台!!!
常用正则表达式最强整理(速查手册)
还在写大量 if 来判断?试试用一个规则执行器来替代它请立即卸载这款 IDEA 插件
最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

明天见(。・ω・。)ノ♡

深入理解JDK动态代理相关推荐

  1. 深入理解JDK动态代理原理,使用javassist动手写一个动态代理框架

    文章目录 系列文章索引 一.动手实现一个动态代理框架 1.初识javassist 2.使用javassist实现一个动态代理框架 二.JDK动态代理 1.编码实现 2.基本原理 (1)getProxy ...

  2. 【Java高级程序设计学习笔记】深入理解jdk动态代理

    java的设计模式中有一项设计模式叫做代理模式,所谓代理模式,就是通过代理方来操作目标对象,而不是自己直接调用.代理又分为静态代理和动态代理,静态代理就是针对每个被代理对象写一个代理类,操作不够优雅: ...

  3. 【干货】JDK动态代理的实现原理以及如何手写一个JDK动态代理

    动态代理 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理模式从类型上来说,可以分为静态代理和动态代理两种类型. 在解 ...

  4. java jdk动态代理学习记录

    转载自: https://www.jianshu.com/p/3616c70cb37b JDK自带的动态代理主要是指,实现了InvocationHandler接口的类,会继承一个invoke方法,通过 ...

  5. JDK 动态代理的简单理解

    http://www.cnblogs.com/luotaoyeah/p/3778183.html JDK 动态代理的简单理解 动态代理 代理模式是 Java 中的常用设计模式,代理类通过调用被代理类的 ...

  6. 一文理解JDK静态代理、JDK动态代理、Cglib动态代理

    代理模式 通过代理来访问真实的对象,而不是直接去访问真正干活的对象,比如二房东租房,二房是代理者,而一房东才是真正的房东:或者说生活中的中介.Spring中的AOP就是动态代理 适用场景 需要动态修改 ...

  7. (转)面试必备技能:JDK动态代理给Spring事务埋下的坑!

    一.场景分析 最近做项目遇到了一个很奇怪的问题,大致的业务场景是这样的:我们首先设定两个事务,事务parent和事务child,在Controller里边同时调用这两个方法,示例代码如下: 1.场景A ...

  8. 【原创】分布式之缓存击穿 【原创】自己动手实现静态资源服务器 【原创】自己动手实现JDK动态代理...

    [原创]分布式之缓存击穿 什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询, ...

  9. 理解Java动态代理(1)—找我还钱?我出钱要你的命

    代理模式是最常用的一个设计模式之一,理解起来也是很简单,一张图足以说明了,LZ就不废话了. 至于代理模式能干嘛也不是LZ今天想说的,今天主要想简单介绍下JAVA里面的动态代理."动" ...

最新文章

  1. 使用nc传输文件和目录【转】
  2. rpm包安装mysql5.6
  3. Eclipse中Maven的安装
  4. java基础-可执行jar包
  5. C#中MySQL语句带参数的模糊匹配问题
  6. Python之路(第三十九篇)管道、进程间数据共享Manager
  7. 北斗导航 | 大规模点云地图的自动化构建(高翔:SLAM十四讲)
  8. [ASP.NET]EF选一个空表的情况
  9. CV算法助理 | 华为外包招聘
  10. 【iCore3 双核心板_FPGA】例程二:GPIO输出实验——点亮三色LED
  11. 视频全程:哈萨比斯首次公开解读AlphaZero
  12. DenyHosts清除黑名单IP地址方法
  13. QCC300x学习笔记:自定义一个GATT client
  14. 推荐两款ios端磁力下载工具
  15. ValueError: operands could not be broadcast together with shapes (100,) (71,)
  16. 软件测试——126邮箱网页注册和登陆功能测试
  17. len函数python_Python len()函数
  18. 文件传输协议FTP/TFTP/SSH/SCP——应用层
  19. 怎么找电脑服务器文档,怎么找到电脑的服务器地址
  20. HTML5系列代码:使用三种方法插入图像

热门文章

  1. linux c编程之fcntl
  2. 2018 蓝桥杯省赛 B 组模拟赛(一)-年龄
  3. 2018.09.01 poj3071Football(概率dp+二进制找规律)
  4. mem 族函数的实现
  5. ES5 数组方法forEach
  6. C# 温故而知新:Stream篇(二)
  7. 关系数据理论中的范式
  8. 【C#串口编程计划】C#串口协议解析 -- 二进制数据
  9. The Six Best Practices(4~6)
  10. postfilter中文什么意思_Filterpost请求中文字符编码的过滤器 --学习笔记