背景
JDK 动态代理存在的一些问题:
JDK 通过反射实现动态代理调用,这意味着低下的调用效率:
每次调用 <code>Method.invoke()</code> 都会检查方法的可见性、校验参数是否匹配,过程涉及到很多 native 调用,具体参见 JNI 调用开销
反射调用涉及动态类解析,这种不可预测性,导致被反射调用的代码无法被 JIT 内联优化,具体参见 反射调用方法
可以通过<code>java.lang.invoke.MethodHandle</code>来规避以上问题,但是这不在本文讨论的范围。
<code>java.lang.reflect.Proxy</code>只支持通过接口生成代理类,这意味着 JDK 动态代理只能代理接口,无法代理具体的类。
对于一些外部依赖或者现有模块来说,无法通过该方式实现动态代理。
应用场景
CGLib 是一款用于实现高效动态代理的字节码增强库,通过字节码生成技术,动态编译生成代理类,从而将反射调用转换为普通的方法调用。
下面通过两个案例体验一下 CGLib 的使用方式。
假设一个输出问候语句的类 <code>Greet</code>,现在有个新需求:在输出内容前后加上姓名,实现个性化输出。下面通过 CGLib 实现该功能:
随着业务发展,系统需要支持法语的问候 <code>FranceGreet</code>,在不修改现有业务代码的前提下,可以通过 CGLib 实现该功能:
原理简析
从前面的案例可以看到,CGLib 使用的方式很简单,大致可以分为两步:
配置 Enhancer
设置需要代理的目标类与接口
通过 <code>Callback</code> 设置需要增强的功能
通过 <code>CallbackFilter</code> 将方法匹配到具体的 <code>Callback</code>
创建代理对象
通过 <code>CallbackFilter</code> 获取方法与 <code>Callback</code> 的关联关系
继承目标类并重写<code>override</code>方法,在调用代码中嵌入 Callback
编译动态生成的字节码生成代理类
通过反射调用构造函数生成代理对象
此外,CGLib 支持多种 Callback,这里简单介绍几种:
<code>NoOp</code> 不使用动态代理,匹配到的方法不会被重写
<code>FixedValue</code> 返回固定值,被代理方法的返回值被忽略
<code>Dispatcher</code> 指定上下文,将代理方法调用委托给特定对象
<code>MethodInterceptor</code> 调用拦截器,用于实现环绕通知<code>around advice</code>
其中 <code>MethodInterceptor</code> 最为常用,可以实现多种丰富的代理特性。
但这类 <code>Callback</code> 也是其中最重的,会导致生成更多的动态类,具体原因后续介绍。
底层通过 <code>Enhancer.generateClass()</code> 生成代理类,其具体过程不作深究,可以简单概括为:
通过<code>ClassVisitor</code>获取目标类信息
通过<code>ClassEmitter</code>调用 asm 库注入增强方法,并生成<code>byte[]</code> 形式的字节码
通过反射调用<code>ClassLoader.defineClass()</code>将<code>byte[]</code>转换为<code>Class</code>对象
将生成完成的代理类缓存至<code>LoadingCache</code>,避免重复生成
通过 arthas 的 jad 命令可以观察到,案例 Weaving 中实际生成了以下类:
目标类:buttercup.test.Greet
代理类:buttercup.test.Greet$$EnhancerByCGLIB(省略后缀)
目标类 FastClass:buttercup.test.Greet$$FastClassByCGLIB(省略后缀)
代理类 FastClass:buttercup.test.Greet$$EnhancerByCGLIB$$FastClassByCGLIB(省略后缀)
代理类就是 <code>Ehancer.create()</code> 中为了创建代理对象动态生成的类,该类不但继承了目标类,并且还重写了需要被代理的方法。其命名规则为:目标类 + $$EnhancerByCGLIB。
在案例一中,我们分别给 <code>Greet.hello()</code> 与 <code>Greet.hi()</code> 分别添加了拦截器<code>Weaving.adviceBefore</code> 与 <code>Weaving.adviceAfter</code>,下面我们分析代理类是如何完成这一功能的:
在动态生成的类中,CGLib 为每个被代理的方法创建了 <code>MethodProxy</code> 对象。
该对象替代了 <code>Method.invoke()</code> 功能,是实现高效方法调用的的关键。下面我们以 <code>Greet.hello()</code> 为例对该类进行分析:
可以看到 <code>MethodProxy</code> 的调用实际是通过 <code>FastClass</code> 完成的,这是 CGLib 实现高性能反射调用的秘诀,下面来解析这个类的细节。
为了规避反射带来的性能消耗,CGLib 定义了 <code>FastClass</code> 来实现高效的方法调用,其主要职责有两个
方法映射:解析 Class 对象并为每个 Constructor 与 Method 指定一个整数索引值 index
方法调用:通过 switch(index) 的方式,将反射调用转化为硬编码调用
其命名规则为:目标类 + $$FastClassByCGLIB。下面具体分析一下对目标类 <code>Greet</code> 对应的 <code>FastClass</code>:
之前提及过:使用 <code>MethodInterceptor</code> 会比其他 <code>Callback</code> 生成更多的动态类,这是因为需要支持 <code>MethodProxy.invokeSuper()</code> 调用:
<code>MethodProxy.invokeSuper()</code> 通过调用代理类中带 <code>$CGLIB$</code> 前缀的方法,绕过被重写的代理方法,避免出现无限递归。
为了保证调用效率,需要对代理类也生成 <code>FastClass</code>:
案例 Introduction 中仅使用了 <code>Dispatcher</code>,因此只生成了代理类,未使用到 <code>FastClass</code>:
本文案例仅涉及 <code>MethodInterceptor</code> 与 <code>Dispatcher</code>,这两个 Callback 也是 Spring AOP 实现的关键,后续将继续分析相关的源码实现。
JIT 编译优化