在讲解动态代理前我们先聊聊什么是静态代理。

静态代理

假设有一天领导突发奇想,给你下发了一个需求:

统计项目中所有类的方法执行耗时。

在拿到需求的那一刻,脑海中冒出来的第一个想法是:

在每个方法的第一行和最后一行加上时间埋点,再打印一行日志不就完事了。

抄起键盘准备开干,想了想又开始犹豫了:

在每个方法都加几行代码,这不是侵入式修改吗?

听架构师大佬说这样的场景可以用代理模式,那尝试一下,具体做法如下。

静态代理的实现

(1)为工程里每个类都写一个代理类,让它与目标类实现同一个接口。图中标红色的就是代理类。

(2)在代理类里面维护一个目标实现类,调用代理类的方法时还是会去调用目标类的方法,只不过在前后加了一些其他逻辑代码。也就是说后面客户端不需要直接调用目标实现类,只需要调用代理类即可,这样就间接调用了对应方法。

用一个公式总结一下:代理类 = 增强代码 + 目标实现类 。

下面这个图中,计算耗时的逻辑就是增强代码。

(3)在所有 new 目标类的地方都替换为 new 代理类,并将目标类作为构造方法参数传入;所有使用目标类调用的地方全部都替换为代理类调用。

如果你看懂了上面的实现方法,那么恭喜你已经掌握了静态代理的核心思想。

静态代理的缺点

静态代理的思路非常简单,就是给每一个目标实现类写一个对应的代理实现类,但是如果一个项目有几千甚至有几万个类,这个工作量可想而知。

前面我们还隐藏了一个假设:每个类都会实现一个接口。那如果一个类没有实现任何接口,代理类如何实现呢?

好了,我们来总结一下静态代理的缺点:

  • 静态代理需要针对每个目标实现类写一个对应的代理类,如果目标类的方法有变动,代理类也要跟着动,维护成本非常高。

  • 静态代理必须依赖接口。

既然知道了静态代理的缺点,那有没有办法实现少些或者不写代理类来实现代理功能呢?答案是有,动态代理。

对象的创建流程

在正式介绍动态代理前,我们先复习一下 java 中对象是如何创建的。

我们在项目中使用一行代码就可以简单创建一个对象,实际上经过的流程还是很复杂的。

// 创建对象
A a = new A();
  • (1)java 源文件经过编译生成字节码文件(.class结尾);

  • (2)类加载器将 class 文件加载到 JVM 内存中,就是常说的方法区,生成 Class 对象;

  • (3)执行 new,申请一块内存区域,紧接着创建一个对象放在 JVM 对象,准确地说是新生代

上面的流程中提到了 Class 对象,有两个概念初学者很容易混淆:Class 对象 和 实例对象

Class 对象简单来说就是 Class 类的实例,Class 类描述了所有的类;实例对象是通过 Class 对象创建出来的。

从上面的分析可以看出来,要想创建一个实例,最最关键的是获得 Class 对象

有些同学可能有疑问了,我写代码的时候创建对象没有用到 Class 对象呀,那是因为 Java 语言底层帮你封装了细节。Java 语言给我们提供了new 这个关键字,new 实在太好用了,一行代码就可以创建一个对象。

我们再回到前面讲的静态代理,静态代理最重要的是提前写一个代理类,有了代理类就可以 new 一个代理对象。但是每次都去写一个代理类是不是太麻烦了?!

再稍微扩展一下思路,有没有办法不写代理类还能生成一个代理对象呢?可以,上面讲的通过代理类 Class 对象就可以生成代理对象,那如何获取代理类 Class 对象呢?我们接着往下看。

动态代理

Class对象包含了一个类的所有信息,如:构造方法、成员方法、成员属性等。

如果我们不写代理类,似乎无法获得代理类 Class 对象,但稍稍动一动脑:代理类和目标类实现的是同一组接口,是不是可以通过接口间接获得代理类 Class 对象。

代理类和目标类实现了同一组接口,这就说明他们大体结构都是一致的,这样我们对代理对象的操作都可以转移到目标对象身上,代理对象只需要专注于增强代码的实现。

上面说了这么多其实是在引入动态代理的概念,动态代理相对于静态代理最大的区别就是不需要事先写好代理类,一般在程序的运行过程中动态产生代理类对象。

动态代理实现之 JDK

JDK 原生提供了动态代理的实现,主要是通过java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler这两个类配合使用。

Proxy类有个静态方法,传入类加载器和一组接口就可以返回代理 Class 对象。

public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)

这个方法的作用简单来说就是,会将你传入一组接口类的结构信息"拷贝"到一个新的 Class 对象中,新的 Class对象带有构造器是可以创建对象的。

一句话总结:Proxy.getProxyClass() 这个静态方法的本质是以 Class 造 Class

拿到了 Class 对象,就可以使用反射创建实例对象了:

// Proxy.getProxyClass 默认会生成一个带参数的构造方法,这里指定参数获取构造方法
Constructor<A> constructor = aClazz.getConstructor(InvocationHandler.class);
// 使用反射创建代理对象
A a1 = constructor.newInstance(new InvocationHandler() {});

眼尖的同学已经看到了,创建实例的时候需要传入一个 InvocationHandler 对象,说明代理对象中必然有一个成员变量去接收。在调用代理对象的方法时实际上会去执行 InvocationHandler 对象的 invoke方法,画个图理解一下:

invoke 方法里可以写增强代码,然后调用目标对象 work 方法。

总结一下流程:

(1)通过 Proxy.getProxyClass() 方法获取代理类 Class 对象;

(2)通过反射 aClazz.getConstructor() 获取构造器对象;

(3)定义InvocationHandler类并实例化,当然也可以直接使用匿名内部类;

(4)通过反射 constructor.newInstance() 创建代理类对象;

(5)调用代理方法;

看了上面的流程,是不是觉得比静态代理还要繁琐,有没有更加优雅的方法?当然有!

为了尽量简化操作,JDK Proxy 类直接提供了一个静态方法:

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

这个方法传入类加载器、一组接口和 InvocationHandler 对象直接就可以返回代理对象了,有了代理对象就可以调用代理方法了,是不是 so easy?!

newProxyInstance方法本质上帮我们省略了获取代理类对象通过代理类对象创建代理类的过程,这些细节全部隐藏了。

所以真正在项目中直接使用newProxyInstance这个方法就好了,上面讲的那些流程是为了方便大家理解整个过程。

看到这里我相信大家应该能看懂JDK 原生动态代理了。

动态代理实现之 cglib

JDK 动态代理,一旦目标类有了明确的接口,完全可以通过接口生成一个代理 Class 对象,通过代理 Class 对象就可以创建代理对象。

这里可以看出 JDK 动态代理有个限制必须要求目标类实现了接口,那加入一个目标类没有实现接口,那岂不是不能使用动态代理了?

cglib 就是为了实现这个目标而出现的,利用asm开源包对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

JDK动态代理与 cglib 动态代理对比

我们通过几个问题简单对比一下 JDK 和 cglib 动态代理的区别。

问题 1:cglib 和 JDK 动态代理的区别?

  • JDK 动态代理:利用 InvocationHandler 加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理

  • cglib 动态代理:利用ASM框架,将目标对象类生成的class文件加载进来,通过修改其字节码生成代理子类

问题 2:cglib 比 JDK快?

  • cglib底层是ASM字节码生成框架,在 JDK 1.6 前字节码生成要比反射的效率高

  • 在 JDK 1.6 之后 JDK 逐步对动态代理进行了优化,在 1.8 的时候 JDK 的效率已经高于 cglib

问题 3:Spring框架什么时候用 cglib 什么时候用 JDK 动态代理?

  • 目标对象生成了接口默认用 JDK 动态代理

  • 如果目标对象没有实现接口,必须采用cglib

  • 当然如果目标对象使用了接口也可以强制使用cglib

小结

使用代理模式可以避免侵入式修改原有代码。代理分为:静态代理和动态代理。

静态代理要求目标类必须实现接口,通过新建代理类并且与目标类实现同一组接口,最终实现通过代理类间接调用目标类的方法。

关于代理类,可以用一个公式总结一下:代理类 = 增强代码 + 目标实现类 。

静态代理必须要求提前写好代理类,使用起来比较繁琐,这就引入了动态代理。

动态代理是在程序运行的过程中动态生成代理类,根据实现方式的不同进而分为:JDK原生动态代理和CGLIB动态代理。

JDK 动态代理通过反射+InvocationHandler 机制动态生成代理类来实现,要求目标类必须实现接口。cglib 不要求目标类实现接口,通过修改字节码方式生成目标类的子类,这就是代理类。

动态代理不仅在 RPC 框架中被使用,还在其他地方有着广泛的应用场景,比如:Spring AOP、测试框架 mock、用户鉴权、日志、全局异常处理、事务处理等。

彻底明白什么是动态代理?相关推荐

  1. 定时器的实现原理 不消耗cpu_一直在使用JDK动态代理, 不明白原理如何实现?

    01.前言 本来动态代理知识点并不在最近文章列表中, 但是在 mybatis 注册 mapper 接口使用到了, 知其然知其所以然 本篇文章是围绕 JDK 动态代理来进行说明, 需要读者掌握基本的反射 ...

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

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

  3. 支撑Java框架的基础技术:泛型,反射,动态代理,cglib

    以Spring为例要想看明白他的源码需要彻底理解Java的一些基础技术泛型,反射同时对于一些高级技术例如动态代理,cglib和字节码技术也需要掌握,下面就按章节来一一说清楚这些技术的核心部分,最后手写 ...

  4. 支撑Spring的基础技术:泛型,反射,动态代理,cglib等

    1.静态代码块和非静态代码块以及构造函数 出自尚学堂视频:<JVM核心机制 类加载全过程 JVM内存分析 反射机制核心原理 常量池理解> public class Parent {stat ...

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

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

  6. Retrofit的动态代理

    都知道Retrofit是通过动态代理来生成代理对象作为网络请求的发起者. 今天就来看下动态代理是怎么操作的.或者说是怎么让一个貌似接口的对象调用它的抽象方法呢? 先来看代码 public static ...

  7. java从静态代理到动态代理的理解

    为什么需要静态代理? 首先解决这个问题之前得先明白什么是静态代理.其实就是二道贩子. - 解释一下图中的乙机构所作的事情就是静态代理,从商业角度上来说,就是找个人帮你做额外的一些服务. 从代码的角度, ...

  8. MyBatis源码解析【4】反射和动态代理

    通过之前的介绍,我们了解了几个组件的生命周期. 它也是我们重要装备之一. 今天我们需要搞一件更加强的装备,叫做反射和动态代理. 如果没有这件装备的话,显然后面的源码boss是打不动的. 顺便说一下,下 ...

  9. 微服务乱码_netcore 之动态代理(微服务专题)

    动态代理配合rpc技术调用远程服务,不用关注细节的实现,让程序就像在本地调用以用. 因此动态代理在微服务系统中是不可或缺的一个技术.网上看到大部分案例都是通过反射自己实现,且相当复杂.编写和调试相当不 ...

最新文章

  1. ora-1031解决一例
  2. ok6410 u-boot-2012.04.01移植六完善MLC NAND支持
  3. free -m 内存
  4. Jsp-Servlet 概要总结[转]
  5. sklearn——决策树
  6. 想象力惊人!只凭一句话,AI就能脑补出动漫小片
  7. AT指令:AT+CPMS
  8. python prettytable格式设置_Python prettytable模
  9. 深度学习笔记(二)——VGG
  10. 百度文库会员制度悄然上线
  11. 【SQL基础】SQLzoo练习
  12. C#panel渐变绘制
  13. Typescript基础知识--学习笔记
  14. 知识资源整理(持续更新)
  15. 若a,b互素,则a必然存在模b的逆元;若a,b不互素,则a必然不存在模b的逆元
  16. 机器学习深版11:HMM模型
  17. 记录一下大三找实习的过程
  18. 沃尔玛Walmart EDI解决方案之812报文解读
  19. 【读书笔记】刘擎西方现代思想讲义——一场观念的探险
  20. 基于睿思BI-开源商业智能系统实现数据快速可视化

热门文章

  1. 如何用计算机画出分子轨道图,径向分布函数、角度分布函数电子云图形的绘制...
  2. socket属性心跳、Nagle 算法
  3. java 单独用this_以下不是Java中this关键字的作用的是()。
  4. 软考集成教程考点精讲之实施定量风险分析
  5. 怎么写小红书koc投放方案?品牌方怎么找koc
  6. mysql使用函数批量插入数据
  7. 怎么做好网络营销推广?
  8. 啤酒销量的时间序列分析
  9. 4、VI/VIM编辑器
  10. python照片转素描_python实现图片转素描效果代码