【问题标题】:How to make a java proxy object to java.nio.ByteBuffer instance?如何为 java.nio.ByteBuffer 实例创建一个 java 代理对象?
【发布时间】:2014-09-17 21:19:26
【问题描述】:

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

问题是java.nio.ByteBuffer类只有私有构造函数,还有一些final方法,所以我不能用javassist.util.proxy.ProxyFactory类创建代理实例。

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

【问题讨论】:

  • 至少不使用javassist,不使用cglib。您能否更详细地解释为什么需要这个? ByteBuffer 是 NIO 的一部分,部分引入是为了非常快的 IO。使用代理(因此反射调用)会破坏性能。所以,再说一遍:为什么?
  • @AlexR,代理对象不用于生产代码执行,它是在应用程序以特殊调试模式运行时创建的。我有一些外国遗留代码缓存类的实例,这些实例使用存储在其内部字段中的 ByteBuffer 实例,事实证明,这些实例在放回缓存后仍然在某个地方使用。我看到 javassist 和 cglib 无法处理这种情况。但是什么可以呢?
  • 我已经简单地检查了javassist CtClass.addConstructorByteBuddy,但它们并不容易弄清楚代理对象创建部分。一些示例代码可能会有所帮助,但我找不到。
  • @AlexR 代理在 Java 中并不慢,这要归功于 JIT 编译器。反射调用是一个简单的优化目标。
  • @KoichiSenada Byte Buddy 和 Javassist 都不关心 instance 创建。它们都适用于 class 创建。然而,Objenesis 关注对象创建,这个库几乎成为了模拟框架的行业标准。

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


【解决方案1】:

请注意,我提出了一个基于我自己的 (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的实例。基于这个假设,我们希望:

  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 方法显然没有被拦截。但是,由于 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 中,我计划引入一个代理构建器,它允许您在加载时重新定义类,而无需了解代理或字节码。

【讨论】:

  • 我已将所有内容组合成一个 TestNG 类,load 方法抛出 IllegalAccessExceptionjava.lang.IllegalAccessError: class net.bytebuddy.renamed.java.nio.ByteBuffer$ByteBuddy$100644077 cannot access its superinterface ByteBuddyTest$ByteBufferProxy
  • 我假设 ByteBufferProxy 在您的解决方案中是包私有的?除非您将创建的proxyType 定义为与ByteBufferProxy 在同一个包中,否则这将不起作用。或者,将其设为public。拦截器也是如此。此外,从默认类型命名模式来看,我假设您使用 0.2.1 版本 - 而不是使用 0.3,它修复了一些小问题。
  • 它工作了,我也用 ObjenesisStd 实例化器替换了实例化,它也继续工作。但是是否有可能像javassist 那样拦截方法来调用某个方法调用处理程序实例? ProxyObject.setHandler 方法接受一个处理程序,该处理程序可以是使用传递给它的原始对象构造的自定义 MethodHandler 实例。
  • Javassist 除了我为您写下的内容外,什么也没做。但是,Javassist 通过要求拦截器成为固定 MethodHandler 类型的实例,为您提供了很大的灵活性。使用 Byte Buddy,您可以使用任何 POJO 甚至静态方法,如上所示。除了使用ProxyObject.setHandler,您可以简单地使用您自己的类型,例如ByteBufferProxy。除了 Javassist,Byte Buddy 不维护类缓存。我在这个答案中解释了原因:stackoverflow.com/questions/23732236/…
【解决方案2】:

我可以建议您 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,包括 ByteBuffers。如果你愿意,你可以使用 javaassist 的代理,尽管恕我直言,它不一定在这里。

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

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

祝你好运。

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 感谢分享。注意 Byte Buddy 0.4 版,它带有一个代理构建器,它允许您通过在类加载期间重新定义类来生成此类代理。
    猜你喜欢
    • 1970-01-01
    • 2012-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-13
    • 1970-01-01
    相关资源
    最近更新 更多