请注意,我提出了一个基于我自己的 (FOSS) 框架 Byte Buddy 的解决方案,但是已经在其中一个 cmets 中提到它作为潜在的解决方案。
这是一个创建子类的简单代理方法。首先,我们介绍一种为ByteBuffers 创建代理的类型:
interface ByteBufferProxy {
ByteBuffer getOriginal();
void setOriginal(ByteBuffer byteBuffer);
}
此外,我们需要引入一个拦截器来使用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 方法显然没有被拦截。但是,由于 ByteBuffer 中的 final 方法仅用作便利方法(例如,put(byte[]) 使用附加参数 put(byte[],int,int) 调用 put(byte[],int,int) 和数组长度),您仍然可以最终拦截任何方法调用,因为这些“最通用”的方法仍然是可覆盖的。您甚至可以通过 Thread.currentCallStack() 跟踪原始调用。
如果您不指定另一个ConstructorStrategy,字节好友通常会复制其超类的所有构造函数。由于没有可访问的构造函数,它只是创建了一个没有构造函数的类,这在 Java 类文件格式中是完全合法的。您不能定义构造函数,因为根据定义,此构造函数需要调用另一个构造函数,这是不可能的。如果你定义了一个没有这个属性的构造函数,你会得到一个VerifierError,只要你不完全禁用验证器(这是一个糟糕的解决方案,因为它使 Java 本质上不安全地运行)。
相反,对于实例化,我们称之为许多模拟框架使用的流行技巧,但需要对 JVM 进行内部调用。请注意,您可能应该使用诸如 Objenesis 之类的库,而不是直接使用 ReflectionFactory,因为当代码在与 HotSpot 不同的 JVM 上运行时,Objenesis 更加健壮。此外,宁可在非生产代码中使用它。但是不要担心性能。当使用可以由 Byte Buddy 为您缓存的反射 Method(通过 cacheMethod = true)时,即时编译器会处理其余部分,并且基本上没有性能开销(请参阅 bytebuddy.net 上的基准测试)有关详细信息。)虽然反射查找很昂贵,但反射调用却不是。
我刚刚发布了 Byte Buddy 0.3 版,目前正在编写文档。在 Byte Buddy 0.4 中,我计划引入一个代理构建器,它允许您在加载时重新定义类,而无需了解代理或字节码。