How to efficiently wrap POJO with Bytebuddy?

2019-07-06 22:23发布

I want to wrap simple POJO class. The thing is I know nothing about that class beforehand, only that it's POJO with setters and getters. I want to substitute this class with my Proxyclass so that every time client calls getter or setter I would be able to intercept that call. So when the call is intercepted, I want to do some pre-get(or set) operation, then invoke the getter(or setter), and then to do some post-get(or set) operations. I'm creating my proxy like that

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

My GetterInterceptor looks like that

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

And setter looks the same.

But when I set and get something from my Proxyclass instance, it goes much slower (1.5-2 times slower), than with initial Pojo class instance. Am I doing something wrong? I believe, there must be some way to make it faster.

Any help is appreciated!

I measure the performance the following way

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

My results for that are

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

3条回答
forever°为你锁心
2楼-- · 2019-07-06 22:30

This is only a partial answer I'm afraid... Reflection is what's slowing you down so to skip reflection your solution needs to be of the form:

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>)))
            )
            ...

Alas I don't know what the bytebuddy voodoo is exactly. It's all in there somewhere.

查看更多
来,给爷笑一个
3楼-- · 2019-07-06 22:47

Most of this slowdown is simply the cost of doing business via reflection.

Consider this test:

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

I get the following results:

class Main$SimplePojo time = 295
class Main$DelegatingPojo time = 328
class Main$ReflectingPojo time = 544

As you can see, using reflection is about twice as slow. This shouldn't be a huge surprise, those try blocks and method.invoke calls mean you're doing a whole bunch of extra work that the base pojo doesn't need to do.

查看更多
\"骚年 ilove
4楼-- · 2019-07-06 22:54

As pointed out, you should avoid the reflective invocation. In Byte Buddy, use the @SuperCall injection:

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

For a setter, you do not need to return a value so you can use a runnable.

查看更多
登录 后发表回答