1

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

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

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

4

3 回答 3

3

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

这是一个创建子类的简单代理方法。首先,我们介绍一种为ByteBuffers 创建代理的类型:

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. 基于这个假设,我们想要:

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

final方法显然没有被拦截。然而,由于这些final方法ByteBuffer仅用作便利方法(例如,使用附加参数和数组长度的put(byte[])调用),您仍然可以最终拦截任何方法调用,因为这些“最通用”的方法仍然是可覆盖的。您甚至可以通过.put(byte[],int,int)0Thread.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 中,我计划引入一个代理构建器,它允许您在加载时重新定义类,而无需了解代理或字节码。

于 2014-09-16T10:52:15.563 回答
1

我可以建议你 2 个解决方案。

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

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

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

ByteBufferextendsBuffer声明了其中一些方法:

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

如您所见,put()这里order()没有返回类型 ofarray()有点令人困惑,但仍然可以使用。因此,如果您只使用这 3 种方法,您可以子类化Buffer并创建通用包装器来包装任何其他方法,Buffer包括ByteBuffers。如果你愿意,你可以使用 javaassist 的代理,尽管恕我直言,它不一定在这里。

其次,更通用但更棘手的解决方案。您可以创建在类加载期间从特定类(在您的情况下)中删除final修饰符的代理。ByteBuffer然后您可以创建 javassist 代理。

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

总之祝你好运。

于 2014-09-16T10:17:54.450 回答
0

感谢@raphw,我设法创建了一个代理对象构造类,它可以作为代理,java.nio.ByteBuffer但是该类具有我无法克服的最终方法,并且它们在所需代码中广泛使用,这些最终方法是Buffer.remaining()and 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();

}
于 2014-09-17T13:19:26.623 回答