请注意,我提出了一个基于我自己的 (FOSS) 框架 Byte Buddy 的解决方案,但是在其中一个评论中已经提到它作为潜在的解决方案。
这是一个创建子类的简单代理方法。首先,我们介绍一种为ByteBuffer
s 创建代理的类型:
interface ByteBufferProxy {
ByteBuffer getOriginal();
void setOriginal(ByteBuffer byteBuffer);
}
此外,我们需要引入一个拦截器来与 a 一起使用MethodDelegation
:
class Interceptor {
@RuntimeType
public static Object intercept(@Origin(cacheMethod = true) Method method,
@This ByteBufferProxy proxy,
@AllArguments Object[] arguments)
throws Exception {
// Do stuff here such as:
System.out.println("Calling " + method + " on " + proxy.getOriginal());
return method.invoke(proxy.getOriginal(), arguments);
}
}
这个拦截器能够拦截任何方法作为@RuntimeType
转换返回类型,以防它不适合Object
签名。由于您只是进行委派,因此您是安全的。请阅读文档以获取详细信息。从注释中可以看出,此拦截器仅适用于ByteBufferProxy
. 基于这个假设,我们想要:
- 创建 的子类
ByteBuffer
。
- 添加一个字段来存储原始(代理)实例。
- 实现
ByteBufferProxy
并实现接口方法以访问存储实例的字段。
- 覆盖所有其他方法以调用我们上面定义的拦截器。
我们可以这样做:
@Test
public void testProxyExample() throws Exception {
// Create proxy type.
Class<? extends ByteBuffer> proxyType = new ByteBuddy()
.subclass(ByteBuffer.class)
.method(any()).intercept(MethodDelegation.to(Interceptor.class))
.defineField("original", ByteBuffer.class, Visibility.PRIVATE)
.implement(ByteBufferProxy.class).intercept(FieldAccessor.ofBeanProperty())
.make()
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
// Create fake constructor, works only on HotSpot. (Use Objenesis!)
Constructor<? extends ByteBufferProxy> constructor = ReflectionFactory
.getReflectionFactory()
.newConstructorForSerialization(proxyType,
Object.class.getDeclaredConstructor());
// Create a random instance which we want to proxy.
ByteBuffer byteBuffer = ByteBuffer.allocate(42);
// Create a proxy and set its proxied instance.
ByteBufferProxy proxy = constructor.newInstance();
proxy.setOriginal(byteBuffer);
// Example: demonstrates interception.
((ByteBuffer) proxy).get();
}
final
方法显然没有被拦截。然而,由于这些final
方法ByteBuffer
仅用作便利方法(例如,使用附加参数和数组长度的put(byte[])
调用),您仍然可以最终拦截任何方法调用,因为这些“最通用”的方法仍然是可覆盖的。您甚至可以通过.put(byte[],int,int)
0
Thread.currentCallStack()
如果您不指定另一个,Byte Buddy 通常会复制其超类的所有构造函数ConstructorStrategy
。由于没有可访问的构造函数,它只是创建了一个没有构造函数的类,这在 Java 类文件格式中是完全合法的。您不能定义构造函数,因为根据定义,此构造函数需要调用另一个构造函数,这是不可能的。如果你定义了一个没有这个属性的构造函数,VerifierError
只要你不完全禁用验证器,你就会得到一个(这是一个糟糕的解决方案,因为它使 Java 本质上不安全地运行)。
相反,对于实例化,我们称之为许多模拟框架使用的流行技巧,但需要对 JVM 进行内部调用。请注意,您可能应该使用诸如Objenesis之类的库,而不是直接使用,ReflectionFactory
因为当代码在与 HotSpot 不同的 JVM 上运行时,Objenesis 更加健壮。此外,宁可在非生产代码中使用它。但是不要担心性能。当使用Method
可以由 Byte Buddy 为您缓存的反射时(通过cacheMethod = true
),即时编译器会处理其余部分,并且基本上没有性能开销(有关详细信息,请参阅bytebuddy.net上的基准测试。)虽然反射查找是昂贵的,反射调用不是。
我刚刚发布了 Byte Buddy 0.3 版,目前正在编写文档。在 Byte Buddy 0.4 中,我计划引入一个代理构建器,它允许您在加载时重新定义类,而无需了解代理或字节码。