Reputation: 22287
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
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
Reputation:
public <TT extends Trick & Treat> void process(TT thing){
//...
}
edit:
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;
}
}
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