2016-10-18 13 views
1

У меня есть два несвязанных классов Java (только *.class, не *.java), как это:Как динамически создать общий класс прокси двух несвязанных классов?

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

и

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

и я хотел бы, чтобы сформировать своего рода прокси класса во время выполнения, который представляет объединение обоих классов и пересылает их в соответствующий экземпляр, и, возможно, бросьте, если это невозможно. Я предполагаю, что это можно сделать с помощью cglib, но я не знаю с чего начать.

Это то, что я хотел бы сделать (псевдокод):

// 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()); 

Или что-то в этом роде. Я хотел бы сделать это полностью динамично, возможно, cglib? Если это невозможно, я бы сделал это с шагом генерации кода между ними?

+0

Я не понимаю, почему вы хотите сделать это динамически? Поскольку в любом случае вам нужно писать интерфейс TrickOrTreat статически ... В качестве ориентира cglib следует использовать только тогда, когда у вас нет возможности узнать типы объектов, которые вы будете иметь во время выполнения, то есть только для кода библиотеки. –

+0

@DLevant Hrm, да. Поскольку я хочу написать 'TrickOrTreat' в моем исходном коде клиента, я должен иметь это как класс заранее. Вы правы, иначе это не имеет смысла. Поэтому я * должен иметь * предыдущий проход с генератором кода. Хорошо, тогда 'cglib' - не правильный инструмент, но что же тогда? 'javaparser' имеет' * .java' как вход, которого у меня нет. Итак ... как идти? – towi

+0

Я до сих пор не понимаю, почему вы не просто пишете все, что вам нужно от руки ... –

ответ

1
  • Если вам нужна функциональность обоих классов/интерфейсов, которые вы можете использовать
public <TT extends Trick & Treat> void process(TT thing){ 
    //... 
} 

редактировать:

  • Внедрение нового интерфейса MyProxyHandler
public interface MyProxyHandler {} 
  • Растяните с интерфейсами классов говорят TreatInterface и TrickInterface

  • Создайте класс, который реализует ProxyManager 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); 
    } 
} 
  • В вашем корпоративных клиентах логика класса получить экземпляр TreatTrickProxyManager

  • В вашем методе

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

Это на самом деле * * удивительно. Я этого не знал. Я прочитаю, https://docs.oracle.com/javase/tutorial/java/generics/bounded.html. Я могу работать с этим, да, но ваше предложение * далека от решения. Он работает только с * интерфейсами * (или одним интерфейсом и одним классом). – towi

+0

И, хотя используется '&' -symbol, «вычисляется» * соединение * (а не * пересечение *) интерфейсов, поскольку он просто ожидает, что оба интерфейса предоставлены 'TT'. Это не совсем то, что мне нужно, но близко и определенно полезно. – towi

+0

У меня не было времени, чтобы полностью расследовать ваш вопрос. В моем ответе есть редактирование - это направление, в котором я бы пошел, чтобы получить функциональные возможности, которые вы ищете. – Zildyan

1

Если вы хотите торговать cglib, вы можете сделать это с помощью Byte Buddy.Я обычно отказываюсь называть это магией, но здесь вы идете:

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(); 
    } 
} 

Обратите внимание, что вы не можете ссылаться на время выполнения сгенерированных типа во время компиляции. Однако это ограничение связано с генерированием кода времени выполнения.

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)); 

Вы можете преодолеть это путем создания вашего TrickOrTreat класса до непосредственного выполнения таким образом, что вы можете ссылаться на тип во время выполнения.

Что касается предлагаемого подхода типа union, для этого требуется, чтобы по крайней мере один класс был типом интерфейса, поскольку Java не поддерживает множественное наследование.

 Смежные вопросы

  • Нет связанных вопросов^_^