Search code examples
javacglibbyte-buddy

How to efficiently relate interface to POJO at runtime using byte buddy?


I have a service that creates and processes thousands of POJOs in a few seconds so my question is: What's the most efficient way to do it?

Here's my current implementation below. Can it be faster?

The factory:

public class ModelProxyFactory {
    public static <T> T factory(Object entityProxy, Class<T> destinationType) throws IllegalAccessException, InstantiationException {
        Class<?> proxyType = new ByteBuddy()
                .subclass(destinationType)
                .implement(ModelProxy.class)
                .method(ElementMatchers.any())
                .intercept(InvocationHandlerAdapter.of(new ModelInvocationHandler(entityProxy)))
                .make().load(destinationType.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                .getLoaded();

        T proxy = (T) proxyType.newInstance();
        return proxy;
    }
}

Interface that I need my POJO to implement:

public interface ModelProxy {
    Object getEntityProxy();
}

My Invocation Handler:

public class ModelInvocationHandler implements InvocationHandler {

    private Object entityProxy;

    public ModelInvocationHandler(Object entityProxy) {
        this.entityProxy = entityProxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (ModelProxy.class.isAssignableFrom(method.getDeclaringClass())) {
            if (method.getName().equals("getEntityProxy")) {
                return entityProxy;
            }
        }
        throw new RuntimeException("This object was not loaded from the database.");
    }
}

For those who are wondering... Yes, I'm trying to replicate the hibernate LazyInitializationException behavior in a POJO and keep the reference of the lazy loaded entity in my POJO via the interface 'ModelProxy'.


Solution

  • Yes, you should use a TypeCache and reuse a single proxy class. Instead of injecting a specific InvocationHandler, use defineField to make one available and change the instrumentation to delegate to this field. You can then implement an additional interface that defines a setter which you use to set this field.