如何将java代理对象转换为java.nio.ByteBuffer实例?

时间:2014-09-16 08:48:18

标签: java proxy-classes javassist cglib byte-buddy

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

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

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

3 个答案:

答案 0 :(得分:3)

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

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

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签名的情况下转换返回类型。因为你只是委托,你是安全的。 Plase阅读文档以获取详细信息。从注释中可以看出,此拦截器仅适用于ByteBufferProxy的实例。基于这个假设,我们想:

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

    @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和数组长度),仍然能够最终拦截任何方法调用,因为这些&#34;最常见的&#34;方法仍然是可以覆盖的。您甚至可以通过Thread.currentCallStack()跟踪原始调用。

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

    相反,对于实例化,我们称之为许多模​​拟框架使用的流行技巧,但需要对JVM进行内部调用。请注意,您应该使用Objenesis之类的库而不是直接使用ReflectionFactory,因为当代码在与HotSpot不同的JVM上运行时,Objenesis会更强大​​。而且,在非生成代码中使用它。但是不要担心性能。当使用Byte Buddy可以为您缓存的反射Method时(通过cacheMethod = true),即时编译器会处理剩下的事情并且基本上没有性能开销(参见基准测试)有关详细信息,请参阅bytebuddy.net。)虽然反射查找很昂贵,但反射调用不是。

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

答案 1 :(得分: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() {

ByteBuffer扩展Buffer,声明了其中一些方法:

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

如您所见,put()order()在此处不存在,array()的返回类型有点令人困惑,但仍然可以使用。 因此,如果您只使用这3种方法,则可以继承Buffer并创建包装任何其他Buffer包括ByteBuffer的通用包装器。如果你想要你可以使用javaassist的代理,虽然恕我直言,它不一定在这里。

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

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

祝你好运。

答案 2 :(得分:0)

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

}