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

0 阅读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.NoOp\text{net.sf.cglib.proxy.NoOp} 时,cglib 所生成的动态代理类是怎样的呢?让我们一起探索吧。

注意:本文所提到的 NoOp\text{NoOp} 都是指 net.sf.cglib.proxy.NoOp\text{net.sf.cglib.proxy.NoOp}

要点

假设类 C\text{C} 中有 method1(...)method_1(...) 方法。我们可以通过如下代码,为 C\text{C} 生成动态代理类 ProxyC\text{Proxy}_\text{C}

(C) Enhancer.create(C.class, NoOp.INSTANCE);

但是 ProxyC\text{Proxy}_\text{C} 中并没有 override C\text{C} 里的方法。所以对 ProxyC\text{Proxy}_\text{C} 的实例: proxyCproxy_C 而言,调用 proxyC.method1(...)proxy_C.method_1(...) 方法时,执行的其实是 C\text{C} 中的 method1(...)method_1(...) 方法。

代码

铺垫

假设有一个 与门 的类 ⬇️

public class AndGate {
    private void validateLength(boolean[] input) {
        if (input.length == 0) {
            throw new IllegalArgumentException("And gate should have at least one input!");
        }
    }

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

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

我们希望用 cglib 为其生成动态代理,但是这个代理类不需要实现任何额外的逻辑(因为 AndGate 的功能已经够用了),那么我们可以用 net.sf.cglib.proxy.NoOp\text{net.sf.cglib.proxy.NoOp}。(这个场景比较牵强,按照我的理解 net.sf.cglib.proxy.NoOp\text{net.sf.cglib.proxy.NoOp} 应该是和 net.sf.cglib.proxy.Callback\text{net.sf.cglib.proxy.Callback} 的其他子接口一起使用的,单独使用它似乎没什么意义)

项目结构

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

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

AndGate.java

package org.example;

public class AndGate {
    private void validateLength(boolean[] input) {
        if (input.length == 0) {
            throw new IllegalArgumentException("And gate should have at least one input!");
        }
    }

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

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

GateFactory.java

package org.example;

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

public class GateFactory {

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

    public static AndGate buildAndGate() {
        return (AndGate) Enhancer.create(AndGate.class, NoOp.INSTANCE);
    }
}

GateFactoryTest.java

package org.example;

import org.junit.Assert;
import org.junit.Test;

public class GateFactoryTest {

    @Test
    public void testBuildAndGate() {
        AndGate andGate = GateFactory.buildAndGate();

        Assert.assertTrue(andGate.calculate(true, true));
        Assert.assertFalse(andGate.calculate(true, false));
        Assert.assertFalse(andGate.calculate(false, false));
        Assert.assertFalse(andGate.calculate(false, false));
    }
}

AndGate/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
    └── AndGate$$EnhancerByCGLIB$$4b09a444.class

7 directories, 3 files

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

image.png

分析

从上一小节的类图可以看出,AndGate$$EnhancerByCGLIB$$4b09a444 中并没有 override calculate(boolean...) 方法。那么当我们用 AndGate$$EnhancerByCGLIB$$4b09a444 的实例调用 calculate(boolean...) 方法时,实际上会执行它的父类(即 org.example.AndGate\text{org.example.AndGate})中的 calculate(boolean...) 方法。从它的 javadoc 也可以印证这一点 ⬇️

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

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.NoOp
它是本文的主角
end note

@enduml

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

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

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

title 三个类的的类图

class org.example.AndGate {
    - void validateLength(boolean[])
    + boolean calculate(boolean...)
}

class org.example.GateFactory {
    + {static} AndGate buildAndGate()
}

class org.example.GateFactoryTest {
    + void testBuildAndGate()
}

@enduml

画 "org.example.AndGate$$EnhancerByCGLIB$$4b09a444 的类图" 用到的代码

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

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

title <i>org.example.AndGate$$EnhancerByCGLIB$$4b09a444</i> 的类图

interface net.sf.cglib.proxy.Factory

class org.example.AndGate {
    - void validateLength(boolean[] input)
    + 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()
}

class org.example.AndGate$$EnhancerByCGLIB$$4b09a444
net.sf.cglib.proxy.Factory <|.. org.example.AndGate$$EnhancerByCGLIB$$4b09a444
org.example.AndGate <|-- org.example.AndGate$$EnhancerByCGLIB$$4b09a444

class org.example.AndGate$$EnhancerByCGLIB$$4b09a444 {
  - boolean CGLIB$BOUND
  + {static} Object CGLIB$FACTORY_DATA
  - {static} final ThreadLocal CGLIB$THREAD_CALLBACKS
  - {static} final Callback[] CGLIB$STATIC_CALLBACKS
  - NoOp CGLIB$CALLBACK_0
  - {static} Object CGLIB$CALLBACK_FILTER
  {static} void CGLIB$STATICHOOK1()
  + AndGate$$EnhancerByCGLIB$$4b09a444()
  + {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[])
  + net.sf.cglib.proxy.Callback getCallback(int)
  + void setCallback(int, Callback)
  + Callback[] getCallbacks()
  + void setCallbacks(Callback[])
}

@enduml