如何制作java.nio.ByteBuffer实例的java代理对象



我有一个公共抽象类java.nio.ByteBuffer实例,它实际上是私有类java.nio.HeapByteBuffer的实例,我需要创建一个代理对象,它会调用一些调用方法处理程序来检查访问权限,然后在实际实例上调用调用的方法。

问题是java.nio.ByteBuffer类只有私有构造函数,也有一些最终方法,因此我不能用javassist.util.proxy.ProxyFactory类创建代理实例。

那么,我如何制作一个代理对象来控制java.nio.ByteBuffer实例的调用,包括那些最终的方法调用呢?

请注意,我正在介绍一个基于我自己(FOSS)框架Byte Buddy的解决方案,但在其中一条评论中已经提到它是一个潜在的解决方案。

下面是一个创建子类的简单代理方法。首先,我们介绍了一种为ByteBuffer s创建代理的类型:

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的实例。基于这一假设,我们希望:

  1. 创建ByteBuffer的子类
  2. 添加一个字段来存储原始(代理的)实例
  3. 实现ByteBufferProxy,并实现访问存储实例字段的接口方法
  4. 覆盖所有其他方法来调用我们上面定义的拦截器

我们可以这样做:

@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();
}

CCD_ 13方法显然没有被截获。然而,由于ByteBuffer中的final方法仅用作方便方法(例如,put(byte[])使用附加参数0和数组长度调用put(byte[],int,int)),因此您最终仍然能够拦截任何方法调用,因为这些"最通用"的方法仍然是可重写的。您甚至可以通过Thread.currentCallStack()跟踪原始调用。

如果不指定另一个ConstructorStrategy,Byte Buddy通常会复制其超类的所有构造函数。由于没有可访问的构造函数,它只需创建一个没有构造函数的类,这在Java类文件格式中是完全合法的。不能定义构造函数,因为根据定义,此构造函数需要调用另一个构造函数,这是不可能的。如果你定义了一个没有这个属性的构造函数,那么只要你不完全禁用验证器,你就会得到一个VerifierError(这是一个糟糕的解决方案,因为它使Java运行本质上不安全)。

相反,对于实例化,我们调用了一个流行的技巧,它被许多模拟框架使用,但需要对JVM进行内部调用。请注意,您可能应该使用诸如Obgenesis之类的库,而不是直接使用ReflectionFactory,因为当代码在不同于HotSpot的JVM上运行时,Obgenesis更健壮。此外,请在非生产代码中使用此选项。但是,不要担心性能。当使用Byte Buddy可以为您缓存的反射式Method(通过cacheMethod = true)时,即时编译器会处理其余部分,并且基本上没有性能开销(有关详细信息,请参阅bytebuddy.net上的基准测试。)虽然反射式查找很贵,但反射式调用则不然。

我刚刚发布了Byte Buddy 0.3版本,目前正在编写文档。在Byte Buddy 0.4中,我计划引入一个代理生成器,它允许您在加载时重新定义类,而不需要了解代理或字节码。

我可以向您推荐两种解决方案。

首先,简单,不通用,但可能对你有用。

据我所见,ByteBuffer有几个包私有构造函数,允许其子类化,以及以下final方法:

public final ByteBuffer put(byte[] src) {
public final boolean hasArray() {
public final byte[] array() {
public final int arrayOffset() {
public final ByteOrder order() {

ByteBuffer扩展了Buffer,它声明了以下一些方法:

public final boolean hasArray() {
public final Object array() {
public final int arrayOffset() {

正如您所看到的,这里没有put()order()array()的返回类型有点混乱,但仍然可以使用。因此,如果您只使用这3种方法,您可以将Buffer子类化,并创建通用包装器来包装任何其他Buffer(包括ByteBuffer)。如果您愿意,您可以使用javaassist的代理,尽管IMHO不一定在这里。

第二,更普遍但更棘手的解决方案。您可以创建一个代理,在类加载期间从特殊类(在您的情况下为ByteBuffer)中删除final修饰符。然后可以创建javassist代理。

第二种解决方案的变化如下。将ByteBuffer源代码复制到单独的项目中。删除final修饰符并对其进行编译,然后将其推入引导类路径。这种解决方案可能比第二种更容易。

祝你好运。

多亏了@raphw,我成功地创建了一个代理对象构造类,它为java.nio.ByteBuffer创建了代理,但该类有我无法克服的最终方法,它们在所需代码中被广泛使用,这些最终方法是Buffer.remaining()Buffer.hasRemaining(),因此它们无法进行代理映射。

但我想分享我所上的课,只是作为一份报告。

public final class CacheReusableCheckerUtils {
        private static ByteBuddy buddy = new ByteBuddy();
        private static Objenesis objenesis = new ObjenesisStd();
        public static <T> T createChecker(T object) {
            return createChecker(new CacheReusableCheckerInterceptor<>(object));
        }
        public static <T> T createChecker(CacheReusableCheckerInterceptor<T> interceptor) {
            return objenesis.getInstantiatorOf(createCheckerClass(interceptor)).newInstance();
        }
        private static <T> Class<? extends T> createCheckerClass(CacheReusableCheckerInterceptor<T> interceptor) {
            Class<T> objectClass = interceptor.getObjectClass();
            Builder<? extends T> builder = buddy.subclass(objectClass);
            builder = builder.implement(CacheReusableChecker.class).intercept(StubMethod.INSTANCE);
            builder = builder.method(MethodMatchers.any()).intercept(MethodDelegation.to(interceptor));
            return builder.make().load(getClassLoader(objectClass, interceptor), Default.WRAPPER).getLoaded();
        }
        private static <T> ClassLoader getClassLoader(Class<T> objectClass, CacheReusableCheckerInterceptor<T> interceptor) {
            ClassLoader classLoader = objectClass.getClassLoader();
            if (classLoader == null) {
                return interceptor.getClass().getClassLoader();
            } else {
                return classLoader;
            }
        }
    }
public class CacheReusableCheckerInterceptor<T> {
    private T object;
    private boolean allowAccess;
    private Throwable denyThrowable;
    public CacheReusableCheckerInterceptor(@NotNull T object) {
        this.object = object;
    }
    @SuppressWarnings("unchecked")
    public Class<T> getObjectClass() {
        return (Class<T>) object.getClass();
    }
    @RuntimeType
    public final Object intercept(@Origin(cacheMethod = true) Method method, @This T proxy, @AllArguments Object[] arguments) {
        try {
            switch (method.getName()) {
                case "allowAccess":
                    allowAccess();
                    return null;
                case "denyAccess":
                    denyAccess();
                    return null;
                default:
                    return invokeMethod(method, arguments);
            }
        } catch (Exception e) {
            throw new CacheReusableCheckerException(method, object, proxy, e);
        }
    }
    private Object invokeMethod(Method method, Object[] arguments) throws IllegalAccessException, InvocationTargetException {
        checkMethodAccess(method.getName());
        return method.invoke(object, arguments);
    }
    private void allowAccess() {
        if (allowAccess) {
            error("double use");
        }
        allowAccess = true;
        onAccessAllowedAfter(object);
    }
    private void denyAccess() {
        if (!allowAccess) {
            error("double free");
        }
        onAccessDeniedBefore(object);
        allowAccess = false;
        denyThrowable = new Throwable();
    }
    private void checkMethodAccess(String name) {
        if (!allowAccess) {
            switch (name) {
                case "hash":
                case "equals":
                case "toString":
                case "finalize":
                    break;
                default:
                    error("use after free");
            }
        }
    }
    private void error(String message) {
        throw new CacheReusableCheckerException(message, denyThrowable);
    }
    protected void onAccessAllowedAfter(T object) {
    }
    protected void onAccessDeniedBefore(T object) {
    }
}
public interface CacheReusableChecker {
    void allowAccess();
    void denyAccess();
}

最新更新