Search code examples
javaproxy-classesbyte-buddy

How to efficiently wrap POJO with Bytebuddy?


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

Solution

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