vendredi 16 mars 2018

Dynamic proxy factory creating concrete instances and wrapping its method calls

I am writing a dynamic proxy factory instantiating and wrapping all calls to concrete instances that extend a specific base class. Given module-local (non-exported) code this inheritance provides me, if not a guarantee, then some assurance that the class being instantiated/proxied actually defines the constructor that matches the base and I can create it with the same set of arguments as for the base. The constructor would take an argument, here Trx trx, which will only be available (to the invocation handler) at the time of making a call on a method of the created proxy.

If Java accepted classes for proxying, then writing such a factory would be a piece of cake, e.g.:

private final TrxExecutor trxExecutor;

public <T extends Base> T newInstance(Class<T> clazz) {
  InvocationHandler invocationHandler = new TheHandler<>(clazz, trxExecutor);
  @SuppressWarnings("unchecked")
  T res = (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ clazz }, invocationHandler);
  return res;
}

private static class TheHandler<T extends Base> implements InvocationHandler {
  private final Class<T> clazz;
  private final TrxExecutor trxExecutor;

  private TheHandler(Class<T> clazz, TrxExecutor trxExecutor) {
    this.clazz = clazz;
    this.trxExecutor = trxExecutor;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) {
    return trxExecutor.execute(TransactionType.ReadOnly, (trx) -> {
      try {
        // here I want some sort of guarantee for constructor (Trx.class) to exist
        Constructor<T> constructor = clazz.getConstructor(Trx.class);
        T instance = constructor.newInstance(trx);
        return method.invoke(instance, args);
      } catch (IllegalAccessException | InvocationTargetException | InstantiationException | NoSuchMethodException ex) {
        throw new RuntimeException(ex);
      }
    });
  }
}

But Java does not support proxying classes. One way around this could be to supply both the interface and class definition to the proxy factory making the class definition extend the interface and the Base:

public <I, T extends Base & I> T newInstance(Class<I> interface, Class<T> clazz)

Unfortunately, Java is not smart enough to accept this as in this case it does not differentiate between classes and interfaces with respect to I and T and I must be an interface for the declaration to work. That is, the following would work:

public <I extends Iface, T extends Base & Iface> T newInstance(Class<I> interface, Class<T> clazz)

where Iface is a defined interface. Well, the problem is that T in this case is not obliged to implement I, they just have common ancestors.

By now the best I could think of was to accept interfaces only and assume each interface implements static TheInterface instance(Trx trx) providing a corresponding default implementation, then I would invoke this static method instead of the constructor per reflection. I would not need to lookup implementations dynamically and code would be clean. The problem with this approach, however, is that there is no way to tell the factory which interfaces to accept (as the only common element is the constructor arguments or that static method on the interface).

Any workaround for the <I, T extends Base & I> declaration or any idea how to implement such a factory otherwise?





Aucun commentaire:

Enregistrer un commentaire