zoukankan      html  css  js  c++  java
  • 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 编译优化

  • 相关阅读:
    TensorFlow Executor解析
    面试复习
    [洛谷]P1880 石子合并问题
    [西建大ACM协会]OJ平台如何使用
    [ACM] 相关OJ及在线运行代码网站
    [MySQL] Win10 安装MySQL5.7.27
    [PTA] PAT(A) 1012 The Best Rank (25 分)
    [PTA] PAT(A) 1011 World Cup Betting (20 分)
    [PTA] PAT(A) 1010 Radix (25 分)
    [PTA] PAT(A) 1009 Product of Polynomials (25 分)
  • 原文地址:https://www.cnblogs.com/buttercup/p/15256335.html
Copyright © 2011-2022 走看看