如何使用Bytebuddy高效地封装POJO对象?

4

我想封装一个简单的POJO类。问题是,我事先不知道该类的任何内容,只知道它是具有setter和getter的POJO。我想用我的代理类替换这个类,这样每当客户端调用getter或setter时,我就能拦截该调用。因此,当调用被拦截时,我想要进行一些预先的get(或set)操作,然后调用getter(或setter),最后再进行一些post-get(或set)操作。 我是这样创建代理的

private Pojo generatePojoProxy(Class<? extends PojoInterface> myPojo) {
    Class<?> PojoProxyClass;
    PojoProxyClass = new ByteBuddy()
            .subclass(myPojo)
            .method(ElementMatchers.nameStartsWith("get"))
            .intercept(MethodDelegation.to(GetterInterceptor.class))
            .method(ElementMatchers.nameStartsWith("set"))
            .intercept(MethodDelegation.to(SetterInterceptor.class))
            .name("Proxy" + myPojo.getName())
            .make()
            .load(myPojo.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
            .getLoaded();
    Object pojoProxyInstance = null;
    try {
        pojoProxyInstance = PojoProxyClass.newInstance();
    } catch (InstantiationException | IllegalAccessException e) {
        e.printStackTrace();
    }
    return (Pojo) pojoProxyInstance;
}

我的GetterInterceptor看起来像这样

public class GetterInterceptor {

@RuntimeType
public static Object intercept(@AllArguments Object[] allArguments, @Origin Method method, @Super(proxyType = TargetType.class) Object delegate) {
    preGetHandle();
    Object result = null;
    try {
       result = method.invoke(delegate, allArguments);
    } catch (InvocationTargetException | IllegalAccessException e) {
        e.printStackTrace();
    }
    postGetHandle();
    return result;
}

private static void preGetHandle() {}

private static void postGetHandle() {}

Setter看起来是一样的。

但是当我从我的Proxyclass实例设置和获取某些内容时,它比使用初始的Pojo类实例慢得多(1.5-2倍)。 我做错了什么吗?我相信,一定有办法让它更快。

非常感谢您的帮助!

我是这样测试性能的:

public class Main {
private static final int LOOP_COUNT = 10_000_000;

public static void main(String[] args) throws InstantiationException, IllegalAccessException {
    Pojo pojo = new Pojo();
    Pojo myProxy = (Pojo) ProxyFactory.getInstance().getProxy(Pojo.class);

    testTime(pojo);
    testTime(myProxy);


}

private static void testTime(Pojo pojo) {
    long startTime = System.currentTimeMillis();
    Random random = new Random();
    long totalSum = 0;

    for (int i = 0; i<LOOP_COUNT; i++){
        pojo.setId(random.nextLong());
        totalSum += pojo.getId();
    }


    long endTime = System.currentTimeMillis();
    System.out.println(pojo.getClass() + " time = " + (endTime-startTime) + " total= " + totalSum);
}

我的结果如下:

class Pojo time = 288 total= 1060564671495946244
class ProxyPojo time = 738 total= 5879857558672375335

你是如何衡量性能变慢了两倍的?这通常是一个棘手且容易误导的过程。 - Elemental
@Elemental 更新了问题,并附上了性能测量! - swasta
测量结果看起来还不错;你的实际性能输出结果可能对那些了解为什么会慢这么多的人有用 - 我现在非常想知道解释是什么。这是一个非常好的问题;欢迎来到SO。 - Elemental
你可能应该解释一下为什么要这样做。这有点疯狂! - Matthew
3个回答

3

正如指出的那样,应该避免使用反射调用。在Byte Buddy中,请使用@SuperCall注入:

public class GetterInterceptor {

  @RuntimeType
  public static Object intercept(@SuperCall Callable<?> zuper) throws Exception {
    preGetHandle();
    try {
       return zuper.call();
    } finally {
      postGetHandle();
    }
  }

  private static void preGetHandle() {}
  private static void postGetHandle() {}
}

对于设置器(setter)来说,你不需要返回一个值,因此可以使用一个可运行的(runnable)程序。

谢谢你的回答,我按照你的建议进行了更改,在我的性能测试中进行了1000万次迭代,差距已经缩小了。但是对于1万次迭代,代理实例仍然可能慢2倍=( 我想知道是否有改进的空间,还是这只是代理的权衡... - swasta
我假设try-catch块会让你付出一点代价。10,000是JIT编译器开始进行优化的临界值。额外的堆栈帧成本并不奇怪。我稍微调整了示例以优化获取器。 - Rafael Winterhalter
在try块中可能应该有'zuper.run()'。现在当我调用getter时,它返回null =[ - swasta

0

很抱歉,这只是一个部分回答...反射是使您变慢的原因,因此要跳过反射,您的解决方案需要采用以下形式:

PojoProxyClass = new ByteBuddy()
            .subclass(myPojo)
            .method(ElementMatchers.nameStartsWith("get"))
            .intercept(
                    MethodDelegation.to(<pre method>)
                        .andThen(MethodDelegation.to(<getter on the proxy>)
                            .andThen(MethodDelegation.to(<post method>)))
            )
            ...

唉,我不知道ByteBuddy的巫术到底是什么。它肯定在其中某个地方。


0

大部分的减速只是通过反射进行业务成本而已。

考虑这个测试:

import java.lang.reflect.Method;
import java.util.Random;

public class Main {
    public interface Pojo {
        public long getId();
        public void setId(long id);
    }

    public static class SimplePojo implements Pojo  {
        private long id;
        @Override public long getId() { return id; }
        @Override public void setId(long id) {this.id = id;}
    }

    public static class DelegatingPojo implements Pojo {
        private final Pojo pojo;

        public DelegatingPojo(Pojo p) {this.pojo = p;}
        @Override public long getId() { return pojo.getId(); }
        @Override public void setId(long id) { pojo.setId(id);  }
    }

    public static class ReflectingPojo implements Pojo {
        private final Object delegate;
        private final Method getmethod;
        private final Method setmethod;

        public ReflectingPojo(Pojo p) throws NoSuchMethodException {
            this.delegate = p;
            this.getmethod = p.getClass().getMethod("getId");
            this.setmethod = p.getClass().getMethod("setId", Long.TYPE);
        }

        @Override public long getId() {
            Object result = null;
            Object[] allarguments = new Object[0];
            try {
                result = getmethod.invoke(delegate, allarguments);
            } catch (InvocationTargetException | IllegalAccessException e) {
                e.printStackTrace();
            }
            return (Long)result;
        }
        @Override public void setId(long id) {
            Object[] allarguments = new Object[]{id};
            try {
                setmethod.invoke(delegate, allarguments);
            } catch (InvocationTargetException | IllegalAccessException e) {
                e.printStackTrace();
            }
            return;
        }
    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException {
        Pojo pojo = new SimplePojo();
        Pojo proxy = new DelegatingPojo(pojo);
        Pojo refl = new ReflectingPojo(pojo);

        testTime(pojo);
        testTime(proxy);
        testTime(refl);
    }

    private static final int LOOP_COUNT = 10_000_000;
    private static void testTime(Pojo pojo) {
        long startTime = System.currentTimeMillis();
        Random random = new Random();

        for (int i = 0; i < LOOP_COUNT; i++) {
            pojo.setId(random.nextLong());
            pojo.getId();
        }


        long endTime = System.currentTimeMillis();
        System.out.println(pojo.getClass() + " time = " + (endTime - startTime));
    }
}

我得到了以下结果:
class Main$SimplePojo time = 295
class Main$DelegatingPojo time = 328
class Main$ReflectingPojo time = 544

正如您所看到的,使用反射大约要慢两倍。这并不是什么大惊小怪的事情,因为try块和method.invoke调用意味着您需要做很多额外的工作,而基本的pojo则不需要。


谢谢你的回答,但问题在于DelegatingPojo必须在运行时创建,因为我对Pojo类一无所知。这就是为什么我试图使用代理和bytebuddy来实现它的原因。 - swasta
好的 - 所以答案不是使用反射。我认为bytebuddy允许这样做,虽然我以前从未见过,所以我不确定。 - Matthew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接