[Java] 从 class 文件看 cglib 对 FixedValue 的处理

14 阅读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.FixedValue\text{net.sf.cglib.proxy.FixedValue},下图单独展示了 net.sf.cglib.proxy.Callback\text{net.sf.cglib.proxy.Callback}net.sf.cglib.proxy.FixedValue\text{net.sf.cglib.proxy.FixedValue} 两者的关系 👇

image.png

要点

x.png

代码

铺垫

假设有一个逻辑门的接口 ⬇️

public interface LogicGate {
    boolean calculate(boolean... input);
}

我们希望实现 true gate (即,总是返回 true 的逻辑门)。一种可行的方法是使用 net.sf.cglib.proxy.FixedValue\text{net.sf.cglib.proxy.FixedValue} 来实现 true gate 的逻辑。

项目结构

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

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

LogicGate.java

package org.example;

public interface LogicGate {
    boolean calculate(boolean... input);
}

GateFactory.java

package org.example;

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.FixedValue;

public class GateFactory {

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

    private static final FixedValue fixedValue = () -> true;

    public static LogicGate buildTrueGate() {
        return (LogicGate) Enhancer.create(LogicGate.class, fixedValue);
    }

}

GateFactoryTest.java

package org.example;

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

import java.lang.reflect.Field;

public class GateFactoryTest {
    private final LogicGate trueGate = GateFactory.buildTrueGate();

    @Test
    public void testBuildAndGate() {
        Assert.assertTrue(trueGate.calculate());
        Assert.assertTrue(trueGate.calculate(false));
        Assert.assertTrue(trueGate.calculate(false, true));
        Assert.assertTrue(trueGate.calculate(false, false));
    }

    @Test
    public void testFixedValue() throws ReflectiveOperationException {
        Field fixedValueGate = GateFactory.class.getDeclaredField("fixedValue");
        fixedValueGate.setAccessible(true);
        FixedValue fixedValue = (FixedValue) fixedValueGate.get(null);

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

        Assert.assertSame(cglibCallback0, fixedValue);
    }

    @Test
    public void testToString() {
        Assert.assertThrows(ClassCastException.class, trueGate::toString);
    }
}

LogicGate/GateFactory/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
    └── LogicGate$$EnhancerByCGLIB$$d5aea67e.class

7 directories, 3 files

LogicGate$$EnhancerByCGLIB$$d5aea67e.class 看起来和 LogicGate 直接相关。我们在 Intellij IDEA (Community Edition) 可以看到前者反编译的结果(完整的结果比较长,这里就不展示了)。下方是 LogicGate$$EnhancerByCGLIB$$d5aea67e 的类图

image.png

分析

Intellij IDEA (Community Edition) 反编译的结果(如下图所示)来看,LogicGate$$EnhancerByCGLIB$$d5aea67e 中的 equals(Object)/hashCode()/toString()/calculate(boolean...) 的处理逻辑很类似,只是对返回值的处理有些差异(我在下图中用红线把有差异的地方标出来了)。 image.png

所以我们着重看一个方法就行了。我们来看看 LogicGate$$EnhancerByCGLIB$$d5aea67e 中的 calculate(boolean...) 方法 ⬇️

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

    Object var2 = var10000.loadObject();
    return var2 == null ? false : (Boolean)var2;
}

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

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

  • LogicGate$$EnhancerByCGLIB$$d5aea67e 中的 CGLIB$CALLBACK_0 字段
  • GateFactory 中的 fixedValue 字段

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

image.png

  • LogicGate$$EnhancerByCGLIB$$d5aea67e 中的 CGLIB$CALLBACK_0 字段
  • GateFactory 中的 fixedValue 字段

既然以上两者是同一个引用,而且后者的 loadObject() 方法总是返回 true,那么如果调用 LogicGate$$EnhancerByCGLIB$$d5aea67e 实例上的 toString() 方法,会不会抛异常呢?(如果调用它的 hashCode() 方法,情况也是类似的)⬇️

image.png

我用如下的单元测试(上文已提供完整代码)验证了一下 ⬇️ 确实会有 java.lang.ClassCastException\text{java.lang.ClassCastException}

image.png

其他

画 "net.sf.cglib.proxy.Callback 和它的子接口" 用到的代码

我用了 PlantUML 的插件来画那张图,所用到的代码如下 ⬇️

@startuml

title <i>net.sf.cglib.proxy.Callback</i> 和它的子接口

interface net.sf.cglib.proxy.Callback
interface net.sf.cglib.proxy.MethodInterceptor
interface net.sf.cglib.proxy.NoOp
interface net.sf.cglib.proxy.LazyLoader
interface net.sf.cglib.proxy.Dispatcher
interface net.sf.cglib.proxy.InvocationHandler
interface net.sf.cglib.proxy.FixedValue #lightgreen

net.sf.cglib.proxy.Callback <|-- net.sf.cglib.proxy.MethodInterceptor
net.sf.cglib.proxy.Callback <|-- net.sf.cglib.proxy.NoOp
net.sf.cglib.proxy.Callback <|-- net.sf.cglib.proxy.LazyLoader
net.sf.cglib.proxy.Callback <|-- net.sf.cglib.proxy.Dispatcher
net.sf.cglib.proxy.Callback <|-- net.sf.cglib.proxy.InvocationHandler
net.sf.cglib.proxy.Callback <|-- net.sf.cglib.proxy.FixedValue

note top of net.sf.cglib.proxy.Callback
这是一个 <i>marker interface</i>
(这个接口里没有定义任何方法)
end note

note top of net.sf.cglib.proxy.FixedValue
它是本文的主角
浅绿色背景只是表示强调它
无特殊语义
end note

@enduml

画 "net.sf.cglib.proxy.Callbacknet.sf.cglib.proxy.FixedValue" 用到的代码

我用了 PlantUML 的插件来画那张图,所用到的代码如下 ⬇️

@startuml

title <i>net.sf.cglib.proxy.Callback</i> 和 <i>net.sf.cglib.proxy.FixedValue</i>

interface net.sf.cglib.proxy.Callback
interface net.sf.cglib.proxy.FixedValue

net.sf.cglib.proxy.Callback <|-- net.sf.cglib.proxy.FixedValue

interface net.sf.cglib.proxy.FixedValue {
    Object loadObject() throws Exception
}

note top of net.sf.cglib.proxy.Callback
这是一个 <i>marker interface</i>
(这个接口里没有定义任何方法)
end note

@enduml

画 "要点" 一图用到的代码

我用了 PlantUML 的插件来画那张图,所用到的代码如下 ⬇️

@startuml

title 要点
caption \n\n
' caption 中的内容只是为了防止掘金平台的水印遮盖图中的文字

interface Callback
interface FixedValue

Callback <|-- FixedValue

interface FixedValue {
    Object loadObject() throws Exception
}

interface SomeInterface {
    + void someMethod()
}

class SomeFixedValue {
    + Object loadObject() throws Exception
}
FixedValue <|-- SomeFixedValue

class "Proxy<sub>SomeInterface</sub>" as P

class P {
    - FixedValue CGLIB$CALLBACK_0
    + final void someMethod()
}

SomeInterface <|-- P

note as n1
通过如下代码可以生成 SomeInterface 的动态代理类: Proxy<sub>SomeInterface</sub>
<code>
(SomeInterface) Enhancer.create(SomeInterface.class, new SomeFixedValue());
</code>
end note

note as n2
SomeInterface 是用户指定的某个接口
<i>cglib</i> 可以为 SomeInterface 生成动态代理类 Proxy<sub>SomeInterface</sub>
当我们用 Proxy<sub>SomeInterface</sub> 的实例调用 someMethod() 方法时,
会执行 SomeFixedValue 中的 loadObject() 方法
(如有必要, 还会对 loadObject() 的返回值进行类型转换)
end note

SomeInterface .. n1: 指定的接口 SomeInterface
n1 .. P: 生成的动态代理类 Proxy<sub>SomeInterface</sub>

note right of SomeFixedValue
由用户提供的 <i>FixedValue</i> 的实现类
end note

P::CGLIB$CALLBACK_0 .. SomeFixedValue: > 该字段持有一个 SomeFixedValue 的实例

header
这张图里是以 <b>接口</b> 为例, 描述了相关的要点
如果用户指定的是某个 <b>类</b> <i>SomeClass</i>, 那么 <i>cglib</i> 也可以用类似的方式进行处理
end header

note left of P::someMethod
可以认为它的代码是这样的 <:point_down:> (可以先忽略其中的 if 语句块)

<code>
public final void someMethod() {
    FixedValue var10000 = this.CGLIB$CALLBACK_0;
    if (var10000 == null) {
        CGLIB$BIND_CALLBACKS(this);
        var10000 = this.CGLIB$CALLBACK_0;
    }

    var10000.loadObject();
}
</code>
end note

@enduml

画 "三个类的类图" 用到的代码

我用了 PlantUML 的插件来画那张图,所用到的代码如下 ⬇️

@startuml
'https://plantuml.com/class-diagram

title 三个类的类图

interface org.example.LogicGate {
    boolean calculate(boolean)
}

class org.example.GateFactory {
    - {static} final FixedValue fixedValue
    + {static} LogicGate buildTrueGate()
}

class org.example.GateFactoryTest {
    - final LogicGate trueGate
    + void testBuildAndGate()
    + void testFixedValue()
    + void testToString()
}

@enduml

画 "org.example.LogicGate$$EnhancerByCGLIB$$d5aea67e 的类图" 用到的代码

我用了 PlantUML 的插件来画那张图,所用到的代码如下 ⬇️

@startuml
'https://plantuml.com/class-diagram

title <i>org.example.LogicGate$$EnhancerByCGLIB$$d5aea67e</i> 的类图

interface org.example.LogicGate
interface net.sf.cglib.proxy.Factory

interface org.example.LogicGate {
    boolean calculate(boolean... input)
}

interface net.sf.cglib.proxy.Factory {
    Object newInstance(Callback callback)
    Object newInstance(Callback[] callbacks)
    Object newInstance(Class[] types, Object[] args, Callback[] callbacks)
    Callback getCallback(int index)
    void setCallback(int index, Callback callback)
    void setCallbacks(Callback[] callbacks)
    Callback[] getCallbacks()
}

org.example.LogicGate <|-- org.example.LogicGate$$EnhancerByCGLIB$$d5aea67e
net.sf.cglib.proxy.Factory <|-- org.example.LogicGate$$EnhancerByCGLIB$$d5aea67e

class org.example.LogicGate$$EnhancerByCGLIB$$d5aea67e {
    - boolean CGLIB$BOUND
    + {static} Object CGLIB$FACTORY_DATA
    - {static} final ThreadLocal CGLIB$THREAD_CALLBACKS
    - {static} final Callback[] CGLIB$STATIC_CALLBACKS
    - FixedValue CGLIB$CALLBACK_0
    - {static} Object CGLIB$CALLBACK_FILTER
    {static} void CGLIB$STATICHOOK1()
    + final boolean equals(Object)
    + final String toString()
    + final int hashCode()
    # final Object clone() throws CloneNotSupportedException
    + final boolean calculate(boolean...)
    + LogicGate$$EnhancerByCGLIB$$d5aea67e()
    + {static} void CGLIB$SET_THREAD_CALLBACKS(Callback[])
    + {static} void CGLIB$SET_STATIC_CALLBACKS(Callback[])
    - {static} final void CGLIB$BIND_CALLBACKS(Object)
    + Object newInstance(Callback[])
    + Object newInstance(Callback)
    + Object newInstance(Class[], Object[], Callback[])
    + Callback getCallback(int)
    + void setCallback(int, Callback)
    + Callback[] getCallbacks()
    + void setCallbacks(Callback[])
}

@enduml

画 "主线逻辑" 用到的代码

我用了 PlantUML 的插件来画那张图,所用到的代码如下 ⬇️

@startwbs
caption \n\n
' caption 的内容是为了防止掘金平台生成的水印遮盖图中的文字

* 主线逻辑
**:将 <i>this.CGLIB$CALLBACK_0</i> 保存在局部变量 <b><i>var10000</i></b> 中
(两者的类型都是 <i>net.sf.cglib.proxy.FixedValue</i>);
** 调用 <b><i>var10000.loadObject()</i><b> 方法
** 将 <b><i>var10000.loadObject()</i><b> 的返回值转化为正确的类型
@endwbs