towi
towi

Reputation: 22287

How do I dynamically create a common proxy class of two unrelated classes?

I have two unrelated java classes (only *.class, no *.java) like this:

public class Trick {
    public String getName() { return "Jack"; }
    public String trick() { ... }
}

and

public class Treat {
    public String getName() { return "John"; }
    public String treat() { ... }
}

and I would like to generate a sort of Proxy class at runtime that represents the union of both classes and forwards them to the respective instance, and maybe throw if that's not possible. I assume that can be done with cglib but I don't know where to start.

This is what I would like to do (pseudocode):

// prepare: generate a common interface
TrickOrTreat trickOrTreat = magic.createUnionInterface(Trick.class, Treat.class);

// use with concrete interface A:
Trick trick = new Trick();
TrickOrTreat proxyA = magic.createProxy(trickOrTreat.class, trick);
System.out.println("trick name: " + proxyA.getName());

// use with concrete interface B:
Treat treat = new Treat();
TrickOrTreat proxyB = magic.createProxy(trickOrTreat.class, treat);
System.out.println("treat name: " + proxyB.getName());

Or something to that effect. I would like to do it completely dynamically, probably cglib-based? If thats not possible I would do it with a code generation step in between?

Upvotes: 3

Views: 1352

Answers (2)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44032

If you are willing to trade in cglib, you can do this with Byte Buddy. I typically refuse to call it magic but here you go:

class Magic {
    Class<?> createUnionInterface(Class<?> a, Class<?> b) {
        DynamicType.Builder<?> builder = new ByteBuddy().makeInterface();
        Set<MethodDescription.SignatureToken> tokens = new HashSet<>();
        for (MethodDescription m : new TypeDescription.ForLoadedType(a)
                .getDeclaredMethods()
                .filter(ElementMatchers.isVirtual())) {
            tokens.add(m.asSignatureToken());
            builder = builder.defineMethod(m.getName(),
                    m.getReturnType(),
                    m.getModifiers()).withoutCode();
        }
        for (MethodDescription m : new TypeDescription.ForLoadedType(b)
                .getDeclaredMethods()
                .filter(ElementMatchers.isVirtual())) {
            if (!tokens.contains(m.asSignatureToken())) {
                builder = builder.defineMethod(m.getName(),
                        m.getReturnType(),
                        m.getModifiers()).withoutCode();
            }
        }
        return builder.make()
                .load(Magic.class.getClassLoader())
                .getLoaded();
    }

    Object createProxy(Class<?> m, final Object delegate) throws Exception {
        return new ByteBuddy()
                .subclass(m)
                .method(new ElementMatcher<MethodDescription>() {
                    @Override
                    public boolean matches(MethodDescription target) {
                        for (Method method : delegate.getClass()
                               .getDeclaredMethods()) {
                            if (new MethodDescription.ForLoadedMethod(method)
                                   .asSignatureToken()
                                   .equals(target.asSignatureToken())) {
                                return true;
                            }
                        }
                        return false;
                    }
                }).intercept(MethodDelegation.to(delegate))
                .make()
                .load(Magic.class.getClassLoader())
                .getLoaded()
                .newInstance();
    }
}

Note that you cannot reference a runtime-generated type at compile-time. This is however a given constraint with runtime code generation.

Magic magic = new Magic();

Class<?> trickOrTreat = magic.createUnionInterface(Trick.class, Treat.class);

Trick trick = new Trick();
Object proxyA = magic.createProxy(trickOrTreat, trick);
System.out.println("trick name: " + trickOrTreat.getDeclaredMethod("getName").invoke(proxyA));

Treat treat = new Treat();
Object proxyB = magic.createProxy(trickOrTreat, treat);
System.out.println("trick name: " + trickOrTreat.getDeclaredMethod("getName").invoke(proxyB));

You can overcome this by generating your TrickOrTreat class prior to runtime such that you can reference the type at runtime.

As for the suggested union-type approach, this would require you to have at least one class to be an interface type as Java does not support multiple inheritance.

Upvotes: 2

user4046031
user4046031

Reputation:

  • If you need functionality of both classes/interfaces you can use
public <TT extends Trick & Treat> void process(TT thing){
    //...
}

edit:

  • Implement new Interface MyProxyHandler
public interface MyProxyHandler {}
  • Extend it with interfaces of classes say TreatInterface and TrickInterface

  • Create class ProxyManager that implements java.lang.reflect.InvocationHandler

public abstract class ProxyManager<T extends MyProxyHandler> implements InvocationHandler {

    protected static String LOCK_OBJECT = new String("LOCK");

    protected T proxyHandler;
    protected List<T> handlers = new ArrayList<>();

    @SuppressWarnings("unchecked")
    public ProxyManager(Class<T> _clazz) {
        proxyHandler = (T) Proxy.newProxyInstance(_clazz.getClassLoader(), new Class[]{_clazz}, this);
    }

    public T getProxy() {
        return proxyHandler;
    }

    public List<T> getHandlers() {
        return handlers;
    }

    public void setHandlers(List<T> handlers) {
        this.handlers = handlers;
    }

    public boolean registerHandler(T handler) {
        synchronized (LOCK_OBJECT) {
            boolean add = true;
            for (T item : this.handlers) {
                if (item.getClass().equals(handler.getClass())) {
                    add = false;
                }
            }
            if (add)
                this.handlers.add(handler);
            return add;
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String result = "";
        for (MyProxyHandler handler : getHandlers()) {
            try {
                //I recommend that methods returns some enum like HANDLED/NOTHANDLED 
                result = (String) method.invoke(handler, args);
                if (result.equals("Some flag"))
                    break;
            } catch (InvocationTargetException e) {
                throw e.getCause();
            }
        }
        return result;
    }
}
  • Extend that class with your concrete class
public class TreatTrickProxyManager<T extends TreatInterface & TreatInterface> extends ProxyManager<T> {
     public TreatTrickProxyManager(Class<T> _clazz) {
          super(_clazz);
     }
}
  • In your bussines logic class get an instance of TreatTrickProxyManager

  • In your method

public void retrieveSomeData(){
     ((TreatTrickProxyManager)getTreatTrickProxyManager().getProxy()).someMethodInvocation()
}

Upvotes: 1

Related Questions