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

15 阅读1分钟

背景

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

注意:本文所提到的 InvocationHandler\text{InvocationHandler} 都是 net.sf.cglib.proxy.InvocationHandler\text{net.sf.cglib.proxy.InvocationHandler} (而非 JDK 中的 java.lang.reflect.InvocationHandler\text{java.lang.reflect.InvocationHandler}

要点

image.png

image.png

代码

铺垫

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

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

我们希望实现 与门。一种可行的方法是使用 net.sf.cglib.proxy.InvocationHandler\text{net.sf.cglib.proxy.InvocationHandler} 来实现 与门 的逻辑。

项目结构

我们在项目顶层执行 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.InvocationHandler;

import java.lang.reflect.Method;

public class GateFactory {

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

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

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) {
            boolean[] input = (boolean[]) args[0];
            validateLength(input);

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

    public static LogicGate buildAndGate() {
        return (LogicGate) Enhancer.create(LogicGate.class, invocationHandler);
    }
}

GateFactoryTest.java

package org.example;

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

import java.lang.reflect.Field;

public class GateFactoryTest {

    private final LogicGate andGate = GateFactory.buildAndGate();

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

    @Test
    public void testInvocationHandler() throws ReflectiveOperationException {
        Field invocationHandlerField = GateFactory.class.getDeclaredField("invocationHandler");
        invocationHandlerField.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) invocationHandlerField.get(null);

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

        Assert.assertSame(invocationHandler, cglibCallback0);
    }
}

LogicGate/GateFactory/GateFactoryTest 这三个类的类图如下 ⬇️

image.png

运行

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

mvn clean test

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

net
└── sf
    └── cglib
        ├── core
        │   └── MethodWrapper$MethodWrapperKey$$KeyFactoryByCGLIB$$d45e49f7.class
        └── proxy
            └── Enhancer$EnhancerKey$$KeyFactoryByCGLIB$$7fb24d72.class
org
└── example
    └── LogicGate$$EnhancerByCGLIB$$580215af.class

7 directories, 3 files

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

c.png

分析

我们来看看 LogicGate$$EnhancerByCGLIB$$580215af 中的 calculate(boolean... var1) 方法 ⬇️

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

        return (Boolean)var10000.invoke(this, CGLIB$calculate$4, new Object[]{var1});
    } catch (Error | RuntimeException var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}    

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

image.png

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

  • LogicGate$$EnhancerByCGLIB$$580215af 中的 CGLIB$CALLBACK_0 字段
  • GateFactory 中的 invocationHandler 字段

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

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

@enduml

画 "主要步骤" 用到的代码

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

@startwbs

* 主要步骤
**: <i>cglib</i> 生成动态代理类
(为便于叙述, 将这个代理类简称为 <b><i>P</i></b>);
***:在代理类 <b><i>P</i></b> 中,
<i>CGLIB$CALLBACK_0</i> 字段保存
我们提供的 <i>net.sf.cglib.proxy.InvocationHandler</i> 实例
(后者简称为 <b><i>ih</i></b>);
**:在调用代理类 <b><i>P</i></b> 的 <i>equals(Object)/toString()/hashCode()</i> 等方法时
可以通过 <i>CGLIB$CALLBACK_0</i> 字段
让 <b><i>ih</i></b> 接管后续的处理逻辑;

@endwbs

画 "cglib 生成的动态代理类的实例可以访问 ih" 用到的代码

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

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

title <i>cglib</i> 生成的动态代理类的实例可以访问 <i>ih</i>
caption \n\n
' caption 的内容是为了防止掘金平台生成的水印遮盖图中的文字

object ih {
}

object p {
   CGLIB$CALLBACK_0
}

p::CGLIB$CALLBACK_0 --> ih

note bottom of p
<i>p</i> 是 <i>cglib</i> 生成的动态代理类的实例
end note

note bottom of ih
<i>ih</i> 是我们提供的 <i>net.sf.cglib.proxy.InvocationHandler</i> 实例
end note

@enduml

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

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

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

title 三个类的的类图

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

class org.example.GateFactory {
    - {static} final net.sf.cglib.proxy.InvocationHandler invocationHandler
    + {static} LogicGate buildAndGate()
}

class org.example.GateFactoryTest {
    - final LogicGate andGate
    + void testBuildAndGate()
    + void testInvocationHandler()
}

@enduml

画 "org.example.LogicGateEnhancerByCGLIBEnhancerByCGLIB580215af 的类图" 用到的代码

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

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

title <i>org.example.LogicGate$$EnhancerByCGLIB$$580215af</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$$580215af
net.sf.cglib.proxy.Factory <|-- org.example.LogicGate$$EnhancerByCGLIB$$580215af

class org.example.LogicGate$$EnhancerByCGLIB$$580215af {
  - boolean CGLIB$BOUND
  + static java.lang.Object CGLIB$FACTORY_DATA
  - static final java.lang.ThreadLocal CGLIB$THREAD_CALLBACKS
  - static final net.sf.cglib.proxy.Callback[] CGLIB$STATIC_CALLBACKS
  - net.sf.cglib.proxy.InvocationHandler CGLIB$CALLBACK_0
  - static java.lang.Object CGLIB$CALLBACK_FILTER
  - static final java.lang.reflect.Method CGLIB$equals$0
  - static final java.lang.reflect.Method CGLIB$toString$1
  - static final java.lang.reflect.Method CGLIB$hashCode$2
  - static final java.lang.reflect.Method CGLIB$clone$3
  - static final java.lang.reflect.Method CGLIB$calculate$4
  static void CGLIB$STATICHOOK1()
  + final boolean equals(java.lang.Object)
  + final java.lang.String toString()
  + final int hashCode()
  # final java.lang.Object clone() throws java.lang.CloneNotSupportedException
  + final boolean calculate(boolean...)
  + org.example.LogicGate$$EnhancerByCGLIB$$580215af()
  + static void CGLIB$SET_THREAD_CALLBACKS(net.sf.cglib.proxy.Callback[])
  + static void CGLIB$SET_STATIC_CALLBACKS(net.sf.cglib.proxy.Callback[])
  - static final void CGLIB$BIND_CALLBACKS(java.lang.Object)
  + java.lang.Object newInstance(net.sf.cglib.proxy.Callback[])
  + java.lang.Object newInstance(net.sf.cglib.proxy.Callback)
  + java.lang.Object newInstance(java.lang.Class[], java.lang.Object[], net.sf.cglib.proxy.Callback[])
  + net.sf.cglib.proxy.Callback getCallback(int)
  + void setCallback(int, net.sf.cglib.proxy.Callback)
  + net.sf.cglib.proxy.Callback[] getCallbacks()
  + void setCallbacks(net.sf.cglib.proxy.Callback[])
}

@enduml

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

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

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

* 主线逻辑
**:将 <i>this.CGLIB$CALLBACK_0</i> 保存在局部变量 <b><i>var10000</i></b> 中
(两者的类型都是 <i>net.sf.cglib.proxy.InvocationHandler</i>);
** 调用 <b><i>var10000.invoke(Object proxy, Method method, Object[] args)</i><b> 方法
***:<b><i>proxy</i></b> 参数: <i>this</i>
(即当前的代理类对象);
***:<b><i>method</i></b> 参数: <i>CGLIB$calculate$4</i>
(它引用 <i>calculate(boolean...)</i> 方法);
*** <b><i>args</i></b> 参数: <i>new Object[]{var1}</i>
@endwbs

参考资料