mercredi 22 septembre 2021

How efficiently create Java proxy object using CGLib (or any other reflection library)

Objective: (as the question title says): to efficiently create proxy (domain) objects using CGLib or any other reflection library (e.g. ByetBuddy?)

Having our domain class (please note Lombok annotations):

@Getter
@Setter
@RequiredArgsConstructor
public class DomainFoo {

    @NonNull
    private final Integer id;

    // some other fields, final or otherwise!

    public void doSomething() {
        // do something here!
    }
}

I'm trying to create a proxy object of type DomainFoo which responds only to getId method with the given ID (see below), otherwise (calling any other method) it throws an UnsupportedOperationException.

I managed to do this using GCLib (Spring version if it matters):

public static final ObjenesisStd OBJENESIS = new ObjenesisStd();

public static Factory newFoo(Integer id) {
    val enhancer = new Enhancer();
    enhancer.setSuperclass(domainClass);
    enhancer.setCallbackType(MethodInterceptor.class);
    val proxyClass = enhancer.createClass();

    // Since there's no default constructor in domains:
    val instantiator = OBJENESIS.getInstantiatorOf(proxyClass);
    val proxyInstance = instantiator.newInstance();

    val factory = (Factory) proxyInstance;
    factory.setCallbacks(new Callback[]{new DomainProxyInterceptor(id)});
    return factory; // see below for why it's called factory!
}

where DomainProxyInterceptor is:

@RequiredArgsConstructor
private class DomainProxyInterceptor implements MethodInterceptor {
    @NonNull
    private final Integer id;
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        if (method.getDeclaringClass() != Object.class && method.getName().equals("getId")) {
            return id;
        } else {
            throw new NotSupportedException(method.getName());
        }
    }
}

Everything works just fine. Now to make it efficient, I'm trying to cache factory and actually use it a factory:

// newFooFactory is the above method rename!
private static final Factor FOO_FACTORY = newFooFactory(0);

public static DomainFoo newFoo(Integer id) {
    val callbacks = new Callback[]{new DomainProxyInterceptor(id)};
    return (DomainFoo) FOO_FACTORY.newInstance(callbacks);
}

But calling the above method throws a NoSuchMethodError:

Exception in thread "main" java.lang.NoSuchMethodError: com/example/demo/DomainFoo$$EnhancerByCGLIB$$8423c3c4.<init>()V (loaded from file:/Users/rad/works/demo/target/classes/ by jdk.internal.loader.ClassLoaders$AppClassLoader@85a856f6) called from class com.example.demo.DomainFoo$$EnhancerByCGLIB$$8423c3c4 (loaded from file:/Users/rad/works/demo/target/classes/ by jdk.internal.loader.ClassLoaders$AppClassLoader@85a856f6).
    at com.example.demo.DomainFoo$$EnhancerByCGLIB$$8423c3c4.newInstance(<generated>)
    at com.example.demo.DomainFactory.proxyFoo(DomainFactory.java:28)
    at com.example.demo.DemoApplication.main(DemoApplication.java:6)

Which presumably is because Factory.newInstance tries to use the normal constructor but it's not there (because we created the object using Objenesis?). I also tried ReflectionFactory to create the factory instance, but it has the same issue.

Questions (they're kinda related):

  1. Do I need to worry about efficiency at all? (Those objects are normal domains, so there's usually too many of them.)
  2. Is there a better way to do the above? In terms of efficiency or otherwise? Specifically what's the right way to instantiate using GCLib when there's no default constructor?
  3. Is there a way to fix the above issue? Resolving NoSuchMethodError failure?
  4. Any other library or solution I could try?

Thanks.





Aucun commentaire:

Enregistrer un commentaire