背景
如 [Java] 从 class 文件看 cglib 对 InvocationHandler 的处理 一文所提到的, 使用 cglib 时,会用到 的子接口 ⬇️
它们的简要类图如下 ⬇️
本文关心的是 ,下图单独展示了 和 两者的关系 👇
要点
代码
铺垫
假设有一个逻辑门的接口 ⬇️
public interface LogicGate {
boolean calculate(boolean... input);
}
我们希望实现 true gate (即,总是返回 true 的逻辑门)。一种可行的方法是使用 来实现 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 这三个类的类图如下 ⬇️
运行
在项目顶层执行如下命令,可以运行单元测试
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 的类图
分析
从 Intellij IDEA (Community Edition) 反编译的结果(如下图所示)来看,LogicGate$$EnhancerByCGLIB$$d5aea67e 中的 equals(Object)/hashCode()/toString()/calculate(boolean...) 的处理逻辑很类似,只是对返回值的处理有些差异(我在下图中用红线把有差异的地方标出来了)。
所以我们着重看一个方法就行了。我们来看看 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;
}
看起来主线逻辑是这样的 ⬇️
那么以下两者是否为同一个引用呢?
LogicGate$$EnhancerByCGLIB$$d5aea67e中的CGLIB$CALLBACK_0字段GateFactory中的fixedValue字段
我用如下的单元测试(上文已提供完整代码)验证了一下 ⬇️ 两者确实为同一个引用
LogicGate$$EnhancerByCGLIB$$d5aea67e中的CGLIB$CALLBACK_0字段GateFactory中的fixedValue字段
既然以上两者是同一个引用,而且后者的 loadObject() 方法总是返回 true,那么如果调用 LogicGate$$EnhancerByCGLIB$$d5aea67e 实例上的 toString() 方法,会不会抛异常呢?(如果调用它的 hashCode() 方法,情况也是类似的)⬇️
我用如下的单元测试(上文已提供完整代码)验证了一下 ⬇️ 确实会有
其他
画 "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.Callback 和 net.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