天天看点

CGLib 简析

背景

 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 编译优化