[Java] 从 class 文件看 cglib 对 Dispatcher 和 LazyLoader 的处理

66 阅读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.LazyLoader\text{net.sf.cglib.proxy.LazyLoader}
  • net.sf.cglib.proxy.Dispatcher\text{net.sf.cglib.proxy.Dispatcher}

下图单独展示了 net.sf.cglib.proxy.Callback\text{net.sf.cglib.proxy.Callback} 和这两者的关系 👇

image.png

要点

Dispatcher 部分

dispatcher.png

LazyLoader 部分

lazy.png

代码

铺垫

下面举的例子有些牵强,因为我没有想出比较好的例子来 😂 大家凑合看吧。假设有一个 与门 的抽象类 ⬇️

public abstract class AbstractAndGate {
    private void validateInputLength(boolean[] input) {
        if (input.length == 0) {
            throw new IllegalArgumentException("Input should have at least 1 element!");
        }
    }

    public boolean calculate(boolean... input) {
        validateInputLength(input);

        for (boolean item : input) {
            if (!item) {
                return false;
            }
        }

        return true;
    }
}

AbstractAndGate 是抽象类,实际可用的 与门 是它的两个子类 ⬇️

  • CheapAndGate
  • ExpensiveAndGate

其中 CheapAndGate 的成本很低,即使在每次调用 calculate(boolean) 方法时,都创建新的 CheapAndGate 实例,也没问题。而 ExpensiveAndGate 的成本很高,我们希望最多只持有一个 ExpensiveAndGate 实例。

项目结构

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

.
├── pom.xml
└── src
    ├── main
    │   └── java
    │       └── org
    │           └── example
    │               ├── AbstractAndGate.java
    │               └── GateFactory.java
    └── test
        └── java
            └── org
                └── example
                    └── GateFactoryTest.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>

AbstractAndGate.java

package org.example;

public abstract class AbstractAndGate {
    private void validateInputLength(boolean[] input) {
        if (input.length == 0) {
            throw new IllegalArgumentException("Input should have at least 1 element!");
        }
    }

    public boolean calculate(boolean... input) {
        validateInputLength(input);

        for (boolean item : input) {
            if (!item) {
                return false;
            }
        }

        return true;
    }
}

/**
 * Assume that CheapAndGate is inexpensive,
 * and it is OK to create a new instance for {@link AbstractAndGate#calculate(boolean...)} call.
 */
final class CheapAndGate extends AbstractAndGate {

    public CheapAndGate() {
        System.out.println("CheapAndGate instance is being created");
    }

    @Override
    public String toString() {
        return "CheapAndGate";
    }
}

/**
 * Assume that ExpensiveAndGate is expensive,
 * and we need to cache it for {@link AbstractAndGate#calculate(boolean...)} calls.
 */
final class ExpensiveAndGate extends AbstractAndGate {

    public ExpensiveAndGate() {
        // Assume that there is some expensive work here
        System.out.println("ExpensiveAndGate instance is being created");
    }

    @Override
    public String toString() {
        return "ExpensiveAndGate";
    }
}

GateFactory.java

package org.example;

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Dispatcher;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.LazyLoader;

public class GateFactory {

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

    private static final Dispatcher dispatcher = CheapAndGate::new;

    private static final LazyLoader lazyLoader = ExpensiveAndGate::new;

    public static AbstractAndGate buildCheapAndGate() {
        return (AbstractAndGate) Enhancer.create(AbstractAndGate.class, dispatcher);
    }

    public static AbstractAndGate buildExpensiveAndGate() {
        return (AbstractAndGate) Enhancer.create(AbstractAndGate.class, lazyLoader);
    }
}

GateFactoryTest.java

package org.example;

import net.sf.cglib.proxy.Dispatcher;
import net.sf.cglib.proxy.LazyLoader;
import org.junit.Assert;
import org.junit.Test;

import java.lang.reflect.Field;

public class GateFactoryTest {

    @Test
    public void testBuildCheapAndGate() {
        AbstractAndGate cheapAndGate = GateFactory.buildCheapAndGate();
        Assert.assertFalse(cheapAndGate.calculate(false, false));
        Assert.assertFalse(cheapAndGate.calculate(false, true));
        Assert.assertTrue(cheapAndGate.calculate(true, true));
    }

    @Test
    public void testBuildExpensiveAndGate() {
        AbstractAndGate expensiveAndGate = GateFactory.buildExpensiveAndGate();
        Assert.assertFalse(expensiveAndGate.calculate(false, false));
        Assert.assertFalse(expensiveAndGate.calculate(false, true));
        Assert.assertTrue(expensiveAndGate.calculate(true, true));
    }

    @Test
    public void testDispatcher() throws ReflectiveOperationException {
        AbstractAndGate cheapAndGate = GateFactory.buildCheapAndGate();

        Field dispatcherField = GateFactory.class.getDeclaredField("dispatcher");
        dispatcherField.setAccessible(true);
        Dispatcher dispatcher = (Dispatcher) dispatcherField.get(null);

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

        Assert.assertSame(cglibCallback0, dispatcher);
    }

    @Test
    public void testLazyLoader() throws ReflectiveOperationException {
        AbstractAndGate expensiveAndGate = GateFactory.buildExpensiveAndGate();

        Field lazyLoaderField = GateFactory.class.getDeclaredField("lazyLoader");
        lazyLoaderField.setAccessible(true);
        LazyLoader lazyLoader = (LazyLoader) lazyLoaderField.get(null);

        Field cglibLazyLoader0Field = expensiveAndGate.getClass().getDeclaredField("CGLIB$CALLBACK_0");
        cglibLazyLoader0Field.setAccessible(true);
        LazyLoader cglibLazyLoader0 = (LazyLoader) cglibLazyLoader0Field.get(expensiveAndGate);

        Assert.assertSame(cglibLazyLoader0, lazyLoader);
    }
}

AbstractAndGate/CheapAndGate/ExpensiveAndGate/GateFactoryT/GateFactoryTest 的类图如下 ⬇️

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
    ├── AbstractAndGate$$EnhancerByCGLIB$$4542421f.class
    └── AbstractAndGate$$EnhancerByCGLIB$$5ca9308f.class

7 directories, 4 files

以下两个文件看起来和 AbstractAndGate 直接相关

  • AbstractAndGate$$EnhancerByCGLIB$$4542421f.class
  • AbstractAndGate$$EnhancerByCGLIB$$5ca9308f.class

我们在 Intellij IDEA (Community Edition) 可以看到这两个 class 文件反编译的结果(但完整的结果比较长,这里就不展示了)。下方是它们的类图 ⬇️

x.png

分析

我们先看 Dispatcher

Dispatcher

IntelliJ IDEA (Community Edition) 反编译的结果(如下图所示)来看,AbstractAndGate$$EnhancerByCGLIB$$5ca9308f 中的下列方法的处理逻辑类似 ⬇️

  • equals(Object)
  • hashCode()
  • toString()
  • calculate(boolean...)

这些方法在调用 loadObject() 方法后,对它的返回值的处理有些差异(我在下图中用红线把有差异的地方标出来了)。

image.png

所以我们着重看一个方法就行了。我们来看看 calculate(boolean...) 方法 ⬇️

public final boolean calculate(boolean... var1) {
    Dispatcher var10000 = this.CGLIB$CALLBACK_0;
    if (var10000 == null) {
        CGLIB$BIND_CALLBACKS(this);
        var10000 = this.CGLIB$CALLBACK_0;
    }

    return ((AbstractAndGate)var10000.loadObject()).calculate(var1);
}

看起来主线逻辑是这样的 ⬇️

image.png

那么以下两者是否为同一个引用呢?

  • AbstractAndGate$$EnhancerByCGLIB$$5ca9308f 中的 CGLIB$CALLBACK_0 字段
  • GateFactory 中的 dispatcher 字段

我用如下的单元测试(上文已提供完整代码)验证了一下 ⬇️ 两者确实为同一个引用

image.png

那么,在代理类中使用 Dispatcher 的主线逻辑就可以这样概括了 ⬇️

image.png

我们再去看看 LazyLoader

LazyLoader

IntelliJ IDEA (Community Edition) 反编译的结果(如下图所示)来看,AbstractAndGate$$EnhancerByCGLIB$$4542421f 中的下列方法的处理逻辑类似 ⬇️

  • equals(Object)
  • hashCode()
  • toString()
  • calculate(boolean...)

image.png 这些方法内部都调用了 CGLIB$LOAD_PRIVATE_0() 方法,后者的逻辑如下 ⬇️

private final synchronized Object CGLIB$LOAD_PRIVATE_0() {
    Object var10000 = this.CGLIB$LAZY_LOADER_0;
    if (var10000 == null) {
        LazyLoader var10001 = this.CGLIB$CALLBACK_0;
        if (var10001 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10001 = this.CGLIB$CALLBACK_0;
        }

        var10000 = this.CGLIB$LAZY_LOADER_0 = var10001.loadObject();
    }

    return var10000;
}

看起来 this.CGLIB$LAZY_LOADER_0 字段的赋值处理是懒式的。假设 this.CGLIB$CALLBACK_0 字段和 org.example.GateFactory\text{org.example.GateFactory}lazyLoader\text{lazyLoader} 字段引用的是同一个对象。最初 this.CGLIB$LAZY_LOADER_0null,下图第 43 行的 if 条件成立,第 50 行会给 this.CGLIB$LAZY_LOADER_0 赋值(所赋的值是 org.example.ExpensiveAndGate\text{org.example.ExpensiveAndGate} 的一个实例)

image.png

那么之后再执行这个方法时,this.CGLIB$LAZY_LOADER_0 已经不是 null 了,下图第 43 行的 if 语句条件不成立,直接执行第 53 行。

image.png

但是假设毕竟是假设,还是要验证一下以下两者是否为同一个引用。

  • AbstractAndGate$$EnhancerByCGLIB$$4542421f 中的 CGLIB$CALLBACK_0 字段
  • GateFactory 中的 lazyLoader 字段

我用如下的单元测试(上文已提供完整代码)验证了一下 ⬇️ 两者确实为同一个引用

image.png

那么,在代理类中使用 LazyLoader 的主线逻辑就可以这样概括了 ⬇️

image.png

其他

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