CGLib 简析

背景

 JDK 动态代理存在的一些问题:

调用效率低

 JDK 通过反射实现动态代理调用,这意味着低下的调用效率:

  1. 每次调用 Method.invoke() 都会检查方法的可见性、校验参数是否匹配,过程涉及到很多 native 调用,具体参见 JNI 调用开销

  2. 反射调用涉及动态类解析,这种不可预测性,导致被反射调用的代码无法被 JIT 内联优化,具体参见 反射调用方法

 可以通过java.lang.invoke.MethodHandle来规避以上问题,但是这不在本文讨论的范围。

只能代理接口

 java.lang.reflect.Proxy只支持通过接口生成代理类,这意味着 JDK 动态代理只能代理接口,无法代理具体的类。

 对于一些外部依赖或者现有模块来说,无法通过该方式实现动态代理。

应用场景

 CGLib 是一款用于实现高效动态代理的字节码增强库,通过字节码生成技术,动态编译生成代理类,从而将反射调用转换为普通的方法调用。

 下面通过两个案例体验一下 CGLib 的使用方式。

案例一:Weaving

 假设一个输出问候语句的类 Greet,现在有个新需求:在输出内容前后加上姓名,实现个性化输出。下面通过 CGLib 实现该功能:


class Greet { // 需要被增强目标类
    public String hello() { return "hello";  }
    public String hi() { return "hi"; }
    public String toString() { return "@Greet"; }
}

public class Weaving { // 模拟切面织入过程

    // 增加 before: 前缀(模拟前置通知)
    static MethodInterceptor adviceBefore = (target, method, args, methodProxy) -> "before:" + methodProxy.invokeSuper(target, args);

    // 增加 :after 后缀(模拟后置通知)
    static MethodInterceptor adviceAfter = (target, method, args, methodProxy) -> methodProxy.invokeSuper(target, args) + ":after";

    // 通知
    static Callback[] advices = new Callback[] { NoOp.INSTANCE/*默认*/, adviceBefore, adviceAfter };

    // 切入点
    static CallbackFilter pointCut = method -> {
        switch (method.getName()) {
            case "hello" : return 1; // hello() 方法植入前置通知
            case "hi" : return 2;  // hi() 方法植入后置通知
            default: return 0; // 其他方法不添加通知
        }
    };

    public static void main(String[] args) throws InterruptedException {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Greet.class);     // 设置目标类
        enhancer.setCallbacks(advices);          // 设置通知 Advice
        enhancer.setCallbackFilter(pointCut);    // 设置切点 PointCut
        Greet greet = (Greet) enhancer.create(); // 创建 Proxy 对象
        System.out.println(greet.hello());
        System.out.println(greet.hi());
        System.out.println(greet.toString());
        TimeUnit.HOURS.sleep(1);
    }
}

案例二:Introduction

 随着业务发展,系统需要支持法语的问候 FranceGreet,在不修改现有业务代码的前提下,可以通过 CGLib 实现该功能:

interface FranceGreet { // 支持新功能的接口
    String bonjour();
}

class FranceGreeting implements Dispatcher { // 新接口的实现
    private final FranceGreet delegate = () -> "bonjour";
    @Override
    public Object loadObject() throws Exception {
        return delegate;
    }
}

class FranceGreetingMatcher implements CallbackFilter { // 将新接口调用委托给 Dispatcher
    @Override
    public int accept(Method method) {
        return method.getDeclaringClass().equals(FranceGreet.class) ? 1 : 0;
    }
}

public class Introduction { // 模拟引入新接口

    public static void main(String[] args) throws InterruptedException {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Greet.class);
        enhancer.setInterfaces(new Class[]{FranceGreet.class}); // 扩展新接口
        enhancer.setCallbacks(new Callback[]{ NoOp.INSTANCE, new FranceGreeting()}); // 实现新接口
        enhancer.setCallbackFilter(new FranceGreetingMatcher()); // 关联接口与实现
        Greet greet = (Greet) enhancer.create();
        System.out.println(greet.hello()); // 原方法不受影响
        System.out.println(greet.hi());
        FranceGreet franceGreet = (FranceGreet) greet;
        System.out.println(franceGreet.bonjour()); // 新接口方法正常调用
        TimeUnit.HOURS.sleep(1);
    }
}

原理简析

 从前面的案例可以看到,CGLib 使用的方式很简单,大致可以分为两步:

  1. 配置 Enhancer
  • 设置需要代理的目标类与接口
  • 通过 Callback 设置需要增强的功能
  • 通过 CallbackFilter 将方法匹配到具体的 Callback

  1. 创建代理对象
  • 通过 CallbackFilter 获取方法与 Callback 的关联关系
  • 继承目标类并重写override方法,在调用代码中嵌入 Callback
  • 编译动态生成的字节码生成代理类
  • 通过反射调用构造函数生成代理对象

Callback 分类

 此外,CGLib 支持多种 Callback,这里简单介绍几种:

  • NoOp 不使用动态代理,匹配到的方法不会被重写
  • FixedValue 返回固定值,被代理方法的返回值被忽略
  • Dispatcher 指定上下文,将代理方法调用委托给特定对象
  • MethodInterceptor 调用拦截器,用于实现环绕通知around advice

 其中 MethodInterceptor 最为常用,可以实现多种丰富的代理特性。
 但这类 Callback 也是其中最重的,会导致生成更多的动态类,具体原因后续介绍。

字节码生成过程

 底层通过 Enhancer.generateClass() 生成代理类,其具体过程不作深究,可以简单概括为:

  1. 通过ClassVisitor获取目标类信息
  2. 通过ClassEmitter调用 asm 库注入增强方法,并生成byte[] 形式的字节码
  3. 通过反射调用ClassLoader.defineClass()byte[]转换为Class对象
  4. 将生成完成的代理类缓存至LoadingCache,避免重复生成

生成的类结构

 通过 arthas 的 jad 命令可以观察到,案例 Weaving 中实际生成了以下类:

  • 目标类:buttercup.test.Greet
  • 代理类:buttercup.test.Greet$$EnhancerByCGLIB(省略后缀)
  • 目标类 FastClass:buttercup.test.Greet$$FastClassByCGLIB(省略后缀)
  • 代理类 FastClass:buttercup.test.Greet$$EnhancerByCGLIB$$FastClassByCGLIB(省略后缀)

代理类

 代理类就是 Ehancer.create() 中为了创建代理对象动态生成的类,该类不但继承了目标类,并且还重写了需要被代理的方法。其命名规则为:目标类 + $$EnhancerByCGLIB
 在案例一中,我们分别给 Greet.hello()Greet.hi() 分别添加了拦截器Weaving.adviceBeforeWeaving.adviceAfter,下面我们分析代理类是如何完成这一功能的:

public class Greet$$EnhancerByCGLIB extends Greet implements Factory {

  private static final Object[] CGLIB$emptyArgs = new Object[0]; // 默认空参数
  private static final Callback[] CGLIB$STATIC_CALLBACKS; // 静态 Callback(忽略)
  private static final ThreadLocal CGLIB$THREAD_CALLBACKS; // 用于给构造函数传递 Callback

  // 通过 MethodProxy 代理 Greet.hell() 方法
  private static final Method CGLIB$hello$0$Method;
  private static final MethodProxy CGLIB$hello$0$Proxy;

  // 通过 MethodProxy 代理 Greet.hi() 方法
  private static final Method CGLIB$hi$1$Method;
  private static final MethodProxy CGLIB$hi$1$Proxy;

  private boolean CGLIB$BOUND; // 判断 Callback 是否已经初始化
  private NoOp CGLIB$CALLBACK_0; // 默认不拦截,直接调用目标类方法
  private MethodInterceptor CGLIB$CALLBACK_1; // Weaving.adviceBefore(增加 before: 前缀)
  private MethodInterceptor CGLIB$CALLBACK_2; // Weaving.adviceAfter(增加 :after 后缀)

  static {
    Greet$$EnhancerByCGLIB.CGLIB$STATICHOOK1();
  }

  static void CGLIB$STATICHOOK1() { // 静态初始化
    CGLIB$THREAD_CALLBACKS = new ThreadLocal();
    Class<?> clazz = Class.forName("buttercup.test.Greet");
    Class<?> clazz2 = Class.forName("buttercup.test.Greet$$EnhancerByCGLIB");
    Method[] methodArray = ReflectUtils.findMethods(new String[]{"hello", "()Ljava/lang/String;", "hi", "()Ljava/lang/String;"}, clazz.getDeclaredMethods());
    CGLIB$hello$0$Method = methodArray[0];
    CGLIB$hello$0$Proxy = MethodProxy.create(clazz, clazz2, "()Ljava/lang/String;", "hello", "CGLIB$hello$0");
    CGLIB$hi$1$Method = methodArray[1];
    CGLIB$hi$1$Proxy = MethodProxy.create(clazz, clazz2, "()Ljava/lang/String;", "hi", "CGLIB$hi$1");
  }

  // 通过 ThreadLocal 传参
  public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] callbackArray) {
    CGLIB$THREAD_CALLBACKS.set(callbackArray);
  }

  public Greet$$EnhancerByCGLIB() {  // 构造函数
    Greet$$EnhancerByCGLIB.CGLIB$BIND_CALLBACKS(this);
  }

  // 设置 Callback 
  private static final void CGLIB$BIND_CALLBACKS(Object object) {
    block2: {
      Object object2;
      Greet$$EnhancerByCGLIB Greet$$EnhancerByCGLIB;
      block3: {
        Greet$$EnhancerByCGLIB = (Greet$$EnhancerByCGLIB) object;
        // 如果已经初始化过,则直接返回
        if (Greet$$EnhancerByCGLIB.CGLIB$BOUND) break block2;
            Greet$$EnhancerByCGLIB.CGLIB$BOUND = true;
        if (object2 = CGLIB$THREAD_CALLBACKS.get()) != null) break block3; // 从 ThreadLocal 获取 Callback 参数
        if ((object2 = CGLIB$STATIC_CALLBACKS) == null) break block2;
      }
      Callback[] callbackArray = (Callback[])object2; // 初始化 Callback 参数
      Greet$$EnhancerByCGLIB greet$$EnhancerByCGLIB = Greet$$EnhancerByCGLIB;
      greet$$EnhancerByCGLIB.CGLIB$CALLBACK_2 = (MethodInterceptor)callbackArray[2];
      greet$$EnhancerByCGLIB.CGLIB$CALLBACK_1 = (MethodInterceptor)callbackArray[1];
      greet$$EnhancerByCGLIB.CGLIB$CALLBACK_0 = (NoOp)callbackArray[0];
    }
  }

  // 工厂方法,创建增强后的对象
  public Object newInstance(Callback[] callbackArray) {
    Greet$$EnhancerByCGLIB.CGLIB$SET_THREAD_CALLBACKS(callbackArray); // 使用 ThreadLocal 传参
    Greet$$EnhancerByCGLIB Greet$$EnhancerByCGLIB = new Greet$$EnhancerByCGLIB();
    Greet$$EnhancerByCGLIB.CGLIB$SET_THREAD_CALLBACKS(null);  // 清空 ThreadLocal
    return Greet$$EnhancerByCGLIB;
  }

  // 重写 hello() 方法通知 CALLBACK_1
  public final String hello() {
    MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_1;
    if (methodInterceptor == null) { // 初始化 CALLBACK_1
      Greet$$EnhancerByCGLIB.CGLIB$BIND_CALLBACKS(this);
      methodInterceptor = this.CGLIB$CALLBACK_1;
    }
    if (methodInterceptor != null) { // 调用拦截器 Weaving.adviceBefore
      return (String)methodInterceptor.intercept(this, CGLIB$hello$0$Method, CGLIB$emptyArgs, CGLIB$hello$0$Proxy);
    }
    return super.hello();
  }

  // 重写 hi() 方法通知 CALLBACK_2
  public final String hi() {
    MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_2;
    if (methodInterceptor == null) { // 初始化 CALLBACK_2
      Greet$$EnhancerByCGLIB.CGLIB$BIND_CALLBACKS(this);
      methodInterceptor = this.CGLIB$CALLBACK_2;
    }
    if (methodInterceptor != null) { // 调用拦截器 Weaving.adviceAfter
      return (String)methodInterceptor.intercept(this, CGLIB$hi$1$Method, CGLIB$emptyArgs, CGLIB$hi$1$Proxy);
    }
    return super.hi();
  }

  // 直接调用目标类的 hello()
  final String CGLIB$hello$0() {
    return super.hello();
  }

  // 直接调用目标类的 hi()
  final String CGLIB$hi$1() {
    return super.hi();
  }

}

 在动态生成的类中,CGLib 为每个被代理的方法创建了 MethodProxy 对象。
 该对象替代了 Method.invoke() 功能,是实现高效方法调用的的关键。下面我们以 Greet.hello() 为例对该类进行分析:

public class MethodProxy {

  private Signature sig1; // 目标类方法签名:hello()Ljava/lang/String;
  private Signature sig2; // 代理类方法签名:CGLIB$hello$0()Ljava/lang/String;

  private MethodProxy.CreateInfo createInfo; /* 省略初始化过程 */

    private static class CreateInfo {
      Class c1; // 目标类 buttercup.test.Greet
      Class c2; // 代理类 buttercup.test.Greet$$EnhancerByCGLIB
    }

  private final Object initLock = new Object();
  private volatile MethodProxy.FastClassInfo fastClassInfo;

    private static class FastClassInfo {
        FastClass f1; // 目标类 FastClass : 
        FastClass f2; // 代理类 FastClass : 
        int i1;       // 方法在 f1 中对应的索引
        int i2;       // 方法在 f2 中对应的索引
    }

    // 只在 MethodProxy 被调用时加载 FastClass,减少不必要的类生成(lazy-init)
    private void init() {
        if (fastClassInfo == null)  {
            synchronized (initLock) {
                if (fastClassInfo == null) {
                    MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                    fci.f1 = helper(ci, ci.c1); // 
                    fci.f2 = helper(ci, ci.c2); // 
                    fci.i1 = fci.f1.getIndex(this.sig1);
                    fci.i2 = fci.f2.getIndex(this.sig2);
                    fastClassInfo = new FastClassInfo();

                }
            }
        }
    }

    // 根据 Class 对象生成 FastClass 
    private static FastClass helper(MethodProxy.CreateInfo ci, Class type) {
      Generator g = new Generator();
      g.setType(type);
      return g.create();
    }

    // 调用 buttercup.test.Greet.hello()
    // 但实际上会调用代理类的 EnhancerByCGLIB.hello() 实现
    public Object invoke(Object obj, Object[] args) throws Throwable {
        init();
        return fastClassInfo.f1.invoke(fci.i1, obj, args);
    }

    // 调用 buttercup.test.Greet$$EnhancerByCGLIB.CGLIB$hello$0()
    // 通过 super.hello() 调用目标类的 Greet.hello() 实现
    public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        init();
        return fastClassInfo.f2.invoke(fci.i2, obj, args);
    }
}

 可以看到 MethodProxy 的调用实际是通过 FastClass 完成的,这是 CGLib 实现高性能反射调用的秘诀,下面来解析这个类的细节。

目标类 FastClass

 为了规避反射带来的性能消耗,CGLib 定义了 FastClass 来实现高效的方法调用,其主要职责有两个

  1. 方法映射:解析 Class 对象并为每个 Constructor 与 Method 指定一个整数索引值 index
  2. 方法调用:通过 switch(index) 的方式,将反射调用转化为硬编码调用
abstract public class FastClass {
    
    // 映射:根据方法名称与参数类型,获取其对应的 index
    public abstract int getIndex(String methodName, Class[] argClass);

    // 调用:根据 index 找到指定的方法,并进行调用
    public abstract Object invoke(int index, Object obj, Object[] args) throws InvocationTargetException;

}

其命名规则为:目标类 + $$FastClassByCGLIB。下面具体分析一下对目标类 Greet 对应的 FastClass

public class Greet$$FastClassByCGLIB extends FastClass {

    public Greet$$FastClassByCGLIB(Class clazz) {
        super(clazz);
    }

    // 获取 index 的最大值
    public int getMaxIndex() {
        return 4; // 当前 FastClass 总共支持 5 个方法
        //索引值分别为 0:hello(), 1:hi(), 2:equals(), 3:hasCode(), 4:toString()
    }

    // 根据方法名称以及参数类型,获取到指定方法对应的 index
    public int getIndex(String methodName, Class[] argClass) {
        switch (methodName.hashCode()) {
            case 3329: {
                if (!methodName.equals("hi")) break;
                switch (argClass.length) {
                    case 0: { return 1; } // hi() 对应 index 为 1
                }
                break;
            }
            case 99162322: {
                if (!methodName.equals("hello")) break;
                switch (argClass.length) {
                    case 0: { return 0; } // hello() 对应 index 为 0
                }
                break;
            }
            /* 忽略 Object 方法 */
        }
        return -1;
    }

    // 根据方法签名,获取到指定方法对应的 index
    public int getIndex(Signature signature) {
        String sig = ((Object)signature).toString();
        switch (sig.hashCode()) {
            case 397774237: {
                if (!sig.equals("hello()Ljava/lang/String;")) break;
                return 0; // hello() 对应 index 为 0
            }
            case 1155503180: {
                if (!sig.equals("hi()Ljava/lang/String;")) break;
                return 1;  // hi() 对应 index 为 1
            }
            /* 忽略 Object 方法 */
        }
        return -1;
    }

    // 方法调用(硬编码调用)
    public Object invoke(int n, Object obj, Object[] args) throws InvocationTargetException {
        Greet greet = (Greet) obj;
        switch (n) { // 通过 index 指定目标函数
            case 0: { return greet.hello(); } // 通过索引 0 调用 hello()
            case 1: { return greet.hi(); }    // 通过索引 1 调用 hi()
            /* 忽略 Object 方法 */
        }
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

    // 构造函数(硬编码调用)
    public Object newInstance(int n, Object[] argClass) throws InvocationTargetException {
        switch (n) {
            case 0: { return new Greet(); }
        }
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }
}

代理类 FastClass

 之前提及过:使用 MethodInterceptor 会比其他 Callback 生成更多的动态类,这是因为需要支持 MethodProxy.invokeSuper() 调用:

public interface MethodInterceptor extends Callback {

    // 所有生成的代理方法都调用此方法
    // 大多数情况需要通过 MethodProxy.invokeSuper() 来实现目标类的调用
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;
}

 MethodProxy.invokeSuper() 通过调用代理类中带 $CGLIB$ 前缀的方法,绕过被重写的代理方法,避免出现无限递归。

 为了保证调用效率,需要对代理类也生成 FastClass

public class Greet$$EnhancerByCGLIB$$FastClassByCGLIB extends FastClass {

    public Object invoke(int n, Object obj, Object[] args) throws InvocationTargetException {
        Greet$$EnhancerByCGLIB greet$$EnhancerByCGLIB = (Greet$$EnhancerByCGLIB)obj;
        switch (n) {
            case 9: { // 调用目标类的原始 Greet.hello() 方法
                return greet$$EnhancerByCGLIB.CGLIB$hello$0();
            }
            case 10: { // 调用目标类的原始 Greet.hi() 方法
                return greet$$EnhancerByCGLIB.CGLIB$hi$1();
            }
            /* 忽略其他 Enhancer 方法 */
        }
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }
    
    public int getIndex(String methodName, Class[] argClass) {
        switch (methodName.hashCode()) {
            case 1837078673: {
                if (!methodName.equals("CGLIB$hi$1")) break;
                switch (argClass.length) {
                    case 0: { return 10;  }
                }
                break;
            }
            case 1891304123: {
                if (!methodName.equals("CGLIB$hello$0")) break;
                switch (argClass.length) {
                    case 0: { return 9; }
                }
                break;
            }
            /* 忽略其他 Enhancer 方法 */
        }
        return -1;
    }

    public int getIndex(Signature signature) {
        String sig = ((Object)signature).toString();
        switch (sig.hashCode()) {
            case -1632605946: {
                if (!sig.equals("CGLIB$hello$0()Ljava/lang/String;")) break;
                return 9;
            }
            case 540391388: {
                if (!sig.equals("CGLIB$hi$1()Ljava/lang/String;")) break;
                return 10;
            }
            /* 忽略其他 Enhancer 方法 */
        }
        return -1;
    }

}

无 FastClass 的情况

 案例 Introduction 中仅使用了 Dispatcher,因此只生成了代理类,未使用到 FastClass

public class Greet$$EnhancerByCGLIB extends Greet implements FranceGreet, Factory {

    private NoOp CGLIB$CALLBACK_0;
    private Dispatcher CGLIB$CALLBACK_1;

    public final String bonjour() {
        Dispatcher dispatcher = this.CGLIB$CALLBACK_1;
        if (dispatcher == null) {
            Greet$$EnhancerByCGLIB.CGLIB$BIND_CALLBACKS(this);
            dispatcher = this.CGLIB$CALLBACK_1;
        }
        return ((FranceGreet)dispatcher.loadObject()).bonjour();
    }

    /* 忽略多余的属性与方法 */
}

 此外,CGLib 无法代理 final 修饰的类与方法,使用时需要注意。

 本文案例仅涉及 MethodInterceptorDispatcher,这两个 Callback 也是 Spring AOP 实现的关键,后续将继续分析相关的源码实现。



附录

JIT 编译优化

posted @ 2021-09-11 23:02  buttercup  阅读(721)  评论(0编辑  收藏  举报