[Java] 从 class 文件看 cglib 对 MethodInterceptor 的处理 (上)

4 阅读1分钟

背景

[Java] 从 class 文件看 cglib 对 InvocationHandler 的处理 一文所提到的, 使用 cglib 时,会用到 net.sf.cglib.proxy.Callback\text{net.sf.cglib.proxy.Callback} 的子接口 ⬇️

  • net.sf.cglib.proxy.MethodInterceptor\text{net.sf.cglib.proxy.MethodInterceptor}
  • net.sf.cglib.proxy.NoOp\text{net.sf.cglib.proxy.NoOp}
  • net.sf.cglib.proxy.LazyLoader\text{net.sf.cglib.proxy.LazyLoader}
  • net.sf.cglib.proxy.Dispatcher\text{net.sf.cglib.proxy.Dispatcher}
  • net.sf.cglib.proxy.InvocationHandler\text{net.sf.cglib.proxy.InvocationHandler}
  • net.sf.cglib.proxy.FixedValue\text{net.sf.cglib.proxy.FixedValue}

它们的简要类图如下 ⬇️

image.png

本文的主角是 net.sf.cglib.proxy.MethodInterceptor\text{net.sf.cglib.proxy.MethodInterceptor},下图单独展示了 net.sf.cglib.proxy.Callback\text{net.sf.cglib.proxy.Callback}net.sf.cglib.proxy.MethodInterceptor\text{net.sf.cglib.proxy.MethodInterceptor} 的关系 👇

image.png

要点

key.png

代码

铺垫

假设我们已经写好了一个简单的 Adder ⬇️

public class Adder {
    public int add(int a, int b) {
        return a + b;
    }
}

现在希望在每次调用 add(int, int) 方法前,都把入参打印出来,那么可以通过动态代理来实现这一功能。

项目结构

我们在项目顶层执行 tree . 命令,会看到如下的结果 ⬇️

.
├── pom.xml
└── src
    ├── main
    │   └── java
    │       └── org
    │           └── example
    │               ├── Adder.java
    │               └── AdderFactory.java
    └── test
        └── java
            └── org
                └── example
                    └── AdderFactoryTest.java

10 directories, 4 files

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>cglib-study</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <!-- Source: https://mvnrepository.com/artifact/cglib/cglib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.5.5</version> <!-- Use a recent version -->
                <configuration>
                    <argLine>--add-opens=java.base/java.lang=ALL-UNNAMED</argLine>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Adder.java

package org.example;

public class Adder {
    public int add(int a, int b) {
        return a + b;
    }
}

AdderFactory.java

package org.example;

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.*;

import java.util.Arrays;

public class AdderFactory {

    static {
        // 将 cglib 生成的类保存到当前目录下
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, ".");
    }

    private static final MethodInterceptor methodInterceptor =
            (obj, method, args, proxy) -> {
                if (method.getName().equals("add")) {
                    String message = String.format("The arguments for the add method are: %s",
                            Arrays.toString(args));
                    System.out.println(message);
                }
                return proxy.invokeSuper(obj, args);
            };

    public static Adder buildAdder() {
        return (Adder) Enhancer.create(Adder.class, methodInterceptor);
    }
}

AdderFactoryTest.java

package org.example;

import net.sf.cglib.proxy.MethodInterceptor;
import org.junit.Assert;
import org.junit.Test;

import java.lang.reflect.Field;

public class AdderFactoryTest {

    @Test
    public void testBuildAdder() {
        Adder adder = AdderFactory.buildAdder();
        int lowerBound = 1;
        int upperBound = 3;
        for (int a = lowerBound; a <= upperBound; a++) {
            for (int b = lowerBound; b <= upperBound; b++) {
                Assert.assertEquals(a + b, adder.add(a, b));
            }
        }
    }

    @Test
    public void testMethodInterceptor() throws ReflectiveOperationException {
        Adder adder = AdderFactory.buildAdder();

        Field methodInterceptorField = AdderFactory.class.getDeclaredField("methodInterceptor");
        methodInterceptorField.setAccessible(true);
        MethodInterceptor methodInterceptor = (MethodInterceptor) methodInterceptorField.get(null);

        Field cglibCallback0Field = adder.getClass().getDeclaredField("CGLIB$CALLBACK_0");
        cglibCallback0Field.setAccessible(true);
        MethodInterceptor cglibCallback0 = (MethodInterceptor) cglibCallback0Field.get(adder);

        Assert.assertSame(cglibCallback0, methodInterceptor);
    }
}

Adder/AdderFactory/AdderFactoryTest 的类图如下 ⬇️

image.png

运行

在项目顶层执行如下命令,可以运行单元测试

mvn clean test

运行后,会看到项目顶层多了 net 和 org 这两个目录。执行 tree net org 命令后,会看到如下结果 ⬇️

net
└── sf
    └── cglib
        ├── core
        │   └── MethodWrapper$MethodWrapperKey$$KeyFactoryByCGLIB$$d45e49f7.class
        └── proxy
            └── Enhancer$EnhancerKey$$KeyFactoryByCGLIB$$7fb24d72.class
org
└── example
    ├── Adder$$EnhancerByCGLIB$$82ff904.class
    ├── Adder$$EnhancerByCGLIB$$82ff904$$FastClassByCGLIB$$8d93dfdd.class
    └── Adder$$FastClassByCGLIB$$2ea8c1e0.class

7 directories, 5 files

以下三个文件看起来和 Adder 直接相关

  • Adder$$EnhancerByCGLIB$$82ff904.class
  • Adder$$EnhancerByCGLIB$$82ff904$$FastClassByCGLIB$$8d93dfdd.class
  • Adder$$FastClassByCGLIB$$2ea8c1e0.class

我们在 Intellij IDEA (Community Edition) 可以看到这些 class 文件反编译的结果(但完整的结果比较长,这里就不展示了)。我们先看看 Adder$$EnhancerByCGLIB$$82ff904.classAdder$$EnhancerByCGLIB$$82ff904 的类图如下 ⬇️

c.png

分析

您是否想过这个问题 ⬇️

org.example.AdderFactory\text{org.example.AdderFactory} 中的 methodInterceptor\text{methodInterceptor} 字段是 net.sf.cglib.proxy.MethodInterceptor\text{net.sf.cglib.proxy.MethodInterceptor} 类型的,它为什么可以调用 proxyproxy 的基类(即,org.example.Adder\text{org.example.Adder})的方法呢?

image.png

我们先看看 Adder$$EnhancerByCGLIB$$82ff904add(int, int) 方法的逻辑 ⬇️ (以下内容由 IntelliJ IDEA (Community Edition) 反编译 Adder$$EnhancerByCGLIB$$82ff904.class 而得到)

public final int add(int var1, int var2) {
    MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if (var10000 == null) {
        CGLIB$BIND_CALLBACKS(this);
        var10000 = this.CGLIB$CALLBACK_0;
    }

    if (var10000 != null) {
        Object var3 = var10000.intercept(this, CGLIB$add$0$Method, new Object[]{new Integer(var1), new Integer(var2)}, CGLIB$add$0$Proxy);
        return var3 == null ? 0 : ((Number)var3).intValue();
    } else {
        return super.add(var1, var2);
    }
}

它的逻辑涉及两个 if 语句块,我们分别来看

image.png

第一个 if 语句块

第一个 if 条件本质上是在判断 this.CGLIB$CALLBACK_0 是否为 null。我用单元测试验证了如下两个字段是同一个引用 ⬇️ (单元测试的完整代码上文已提供)

  • org.example.AdderFactory\text{org.example.AdderFactory} 中的 methodInterceptor\text{methodInterceptor} 字段
  • Adder$$EnhancerByCGLIB$$82ff904 中的 CGLIB$CALLBACK_0 字段

image.png

所以,可以简单认为第一个 if 条件 不成立

第二个 if 语句块

刚才已经通过单元测试验证了以下两个字段是同一个引用 ⬇️ 那么第二个 if 条件 成立

  • org.example.AdderFactory\text{org.example.AdderFactory} 中的 methodInterceptor\text{methodInterceptor} 字段
  • Adder$$EnhancerByCGLIB$$82ff904 中的 CGLIB$CALLBACK_0 字段

第二个 if 语句块的内容如下

if (var10000 != null) {
    Object var3 = var10000.intercept(this, CGLIB$add$0$Method, new Object[]{new Integer(var1), new Integer(var2)}, CGLIB$add$0$Proxy);
    return var3 == null ? 0 : ((Number)var3).intValue();
} else {
    return super.add(var1, var2);
}

既然 if 条件成立,那么实际执行的就是如下的逻辑 ⬇️ (为了方便阅读,我把代码格式调整了一下)

Object var3 = var10000.intercept(
    this, 
    CGLIB$add$0$Method, 
    new Object[]{new Integer(var1), new Integer(var2)}, 
    CGLIB$add$0$Proxy
);
return var3 == null ? 0 : ((Number)var3).intValue();

其中 var10000org.example.AdderFactory\text{org.example.AdderFactory} 中的 methodInterceptor\text{methodInterceptor} 字段是同一个引用,那么 var10000.intercept(...) 其实就是在调用下图红框对应的那个方法

image.png

形参实参
obj\text{obj}this\text{this} (即,代理类 Adder$$EnhancerByCGLIB$$82ff904 的一个实例)
method\text{method}CGLIB$add$0$Method (引用 org.example.Adder\text{org.example.Adder}add(int, int)\text{add(int, int)} 方法的 Method\text{Method} 对象)
args\text{args}var1\text{var1}var2\text{var2} 组成的 Object 数组
proxy\text{proxy}CGLIB$add$0$Proxy (它比较复杂,下文再细说)

前面三个参数容易解释它们的作用 ⬇️

因为我们在用动态代理,所以需要当前对象的实例,当前方法的引用,当前方法的参数(这也就是前三个参数提供的内容)。只有最后一个参数比较复杂。

CGLIB$add$0$Proxy 是什么

Adder$$EnhancerByCGLIB$$82ff904.class 反编译的完整结果比较长,我们只看其中一部分内容 ⬇️ (本文不关心的内容都用 ... 表示)

package org.example;

...

public class Adder$$EnhancerByCGLIB$$82ff904 extends Adder implements Factory {
    ...
    private MethodInterceptor CGLIB$CALLBACK_0;
    ...
    private static final Method CGLIB$add$0$Method;
    private static final MethodProxy CGLIB$add$0$Proxy;
    ...

    static void CGLIB$STATICHOOK1() {
        ...
        Class var0 = Class.forName("org.example.Adder$$EnhancerByCGLIB$$82ff904");
        Class var1;
        ...
        CGLIB$add$0$Method = ReflectUtils.findMethods(new String[]{"add", "(II)I"}, (var1 = Class.forName("org.example.Adder")).getDeclaredMethods())[0];
        CGLIB$add$0$Proxy = MethodProxy.create(var1, var0, "(II)I", "add", "CGLIB$add$0");
    }

    final int CGLIB$add$0(int var1, int var2) {
        return super.add(var1, var2);
    }

    public final int add(int var1, int var2) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            Object var3 = var10000.intercept(this, CGLIB$add$0$Method, new Object[]{new Integer(var1), new Integer(var2)}, CGLIB$add$0$Proxy);
            return var3 == null ? 0 : ((Number)var3).intValue();
        } else {
            return super.add(var1, var2);
        }
    }
    
    ...

    static {
        CGLIB$STATICHOOK1();
    }
}

可以简单将 CGLIB$add$0$Proxy 的赋值逻辑简化如下 ⬇️

CGLIB$add$0$Proxy = MethodProxy.create(
        Class.forName("org.example.Adder"),
        Class.forName("org.example.Adder$$EnhancerByCGLIB$$82ff904"),
        "(II)I", 
        "add", 
        "CGLIB$add$0"
);  

CGLIB$add$0$Proxy 的类型是 net.sf.cglib.proxy.MethodProxy\text{net.sf.cglib.proxy.MethodProxy},和 add(int, int) 方法对应的 net.sf.cglib.proxy.MethodProxy\text{net.sf.cglib.proxy.MethodProxy} 的简要类图如下 ⬇️(类图中只画了本文关心的内容)

image.png

invoke(Object obj, Object[] args) 方法

当我们调用这个 net.sf.cglib.proxy.MethodProxy\text{net.sf.cglib.proxy.MethodProxy}invoke(Object obj, Object[] args) 方法时,它会通过某种方式(细节要到下一篇文章再展开了)执行 obj.add(((Number)args[0]).intValue(), ((Number)args[1]).intValue()),而 obj 是动态代理类 Adder$$EnhancerByCGLIB$$82ff904 的实例,那么这里就会出现一个递归的调用

image.png

invokeSuper(Object obj, Object[] args) 方法

当我们调用这个 net.sf.cglib.proxy.MethodProxy\text{net.sf.cglib.proxy.MethodProxy}invokeSuper(Object obj, Object[] args) 方法时,它会通过某种方式(细节要到下一篇文章再展开了)执行 obj.CGLIB$add$0(((Number)args[0]).intValue(), ((Number)args[1]).intValue()),而 obj 是动态代理类 Adder$$EnhancerByCGLIB$$82ff904 的实例。Adder$$EnhancerByCGLIB$$82ff904 中的 CGLIB$add$0(int, int) 方法是这样的 ⬇️

final int CGLIB$add$0(int var1, int var2) {
    return super.add(var1, var2);
}

Adder$$EnhancerByCGLIB$$82ff904 的基类是 org.example.Adder\text{org.example.Adder}。这样就可以调用 org.example.Adder\text{org.example.Adder} 中的 add(int, int) 方法了。

其他

我把使用 PlantUML 插件绘制本文中若干图片的原始代码保存在 这篇笔记 里了