2016-01-04 14 views
9

Il problema è creare una versione potenziata dinamica di oggetti esistenti.Come implementare un decoratore wrapper in Java?

Non riesco a modificare l'oggetto Class. Invece devo:

  • sottoclasse esso
  • avvolgere l'oggetto esistente nel nuovo Class
  • delegato tutto il metodo originale prevede all'oggetto avvolto
  • implementare tutti i metodi che sono definiti da un'altra interfaccia

L'interfaccia di aggiungere oggetti esistenti è:

public interface EnhancedNode { 

    Node getNode(); 
    void setNode(Node node); 

    Set getRules(); 
    void setRules(Set rules); 

    Map getGroups(); 
    void setGroups(Map groups); 

} 

Con Byte Buddy Sono riuscito a creare sottoclasse e implementare la mia interfaccia. Il problema è la delega all'oggetto spostato. L'unico modo per fare ciò che ho trovato è l'uso della riflessione, cosa troppo lenta (ho un carico pesante sull'applicazione e le prestazioni sono fondamentali).

Finora il mio codice è:

Class<? extends Node> proxyType = new ByteBuddy() 
    .subclass(node.getClass(), ConstructorStrategy.Default.IMITATE_SUPER_TYPE_PUBLIC) 
    .method(anyOf(finalNode.getClass().getMethods())).intercept(MethodDelegation.to(NodeInterceptor.class)) 
    .defineField("node", Node.class, Visibility.PRIVATE) 
    .implement(EnhancedNode.class).intercept(FieldAccessor.ofBeanProperty()) 
    .defineField("groups", Map.class, Visibility.PRIVATE) 
    .implement(EnhancedNode.class).intercept(FieldAccessor.ofBeanProperty()) 
    .defineField("rules", Set.class, Visibility.PRIVATE) 
    .implement(EnhancedNode.class).intercept(FieldAccessor.ofBeanProperty()) 
    .make() 
    .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) 
    .getLoaded(); 
enhancedClass = (Class<N>) proxyType; 
EnhancedNode enhancedNode = (EnhancedNode) enhancedClass.newInstance(); 
enhancedNode.setNode(node); 

dove Node è l'oggetto di sottoclasse/involucro. Lo NodeInterceptor inoltra i metodi richiamati alla proprietà getNode.

Ecco il codice del NodeInterceptor:

public class NodeInterceptor { 

    @RuntimeType 
    public static Object intercept(@Origin Method method, 
           @This EnhancedNode proxy, 
           @AllArguments Object[] arguments) 
     throws Exception { 
     Node node = proxy.getNode(); 
     Object res; 
     if (node != null) { 
      res = method.invoke(method.getDeclaringClass().cast(node), arguments); 
     } else { 
      res = null; 
     } 
     return res; 
    } 
} 

Tutto sta funzionando, ma il metodo di intercettazione è troppo lento, ho intenzione di utilizzare ASM direttamente per aggiungere l'attuazione di tutti i metodi di Node, ma ci spero è un modo più semplice usando Byte Buddy.

+0

Non conosco Byte-Buddy, ma vedo la possibilità che una nuova classe di proxy venga creata più e più volte. C'è qualche tipo di memorizzazione nella cache? – JimmyB

+0

Btw, come si fa a rispettare il fatto che la classe del nodo specificato abbia un costruttore pubblico predefinito? – JimmyB

+0

Hai un'interfaccia, aggiungi campi e metodi ... Perché non puoi semplicemente creare una classe wrapper da solo senza questo miglioramento dinamico? – AdamSkywalker

risposta

5

probabilmente si desidera utilizzare un Pipe piuttosto che l'API di riflessione:

public class NodeInterceptor { 

    @RuntimeType 
    public static Object intercept(@Pipe Function<Node, Object> pipe, 
           @FieldValue("node") Node proxy) throws Exception { 
     return proxy != null 
     ? pipe.apply(proxy); 
     : null; 
    } 
} 

Per poter utilizzare un tubo, è necessario prima di installarlo. Se disponi di Java 8, puoi utilizzare java.util.Function. In caso contrario, è sufficiente definire un tipo:

interface Function<T, S> { S apply(T t); } 

da soli. Il nome del tipo e il metodo sono irrilevanti. Installare il tipo:

MethodDelegation.to(NodeInterceptor.class) 
       .appendParameterBinder(Pipe.Binder.install(Function.class)); 

Sei sicuro che la parte di riflessione sia il punto critico dei problemi di prestazioni della tua applicazione? Stai memorizzando nella cache le classi generate correttamente e la tua cache funziona in modo efficiente? L'API di riflessione è più veloce della sua reputazione, soprattutto perché l'uso di Byte Buddy tende a implicare siti di chiamata monomorfici.

Infine, un feedback generale. Chiama

.implement(EnhancedNode.class).intercept(FieldAccessor.ofBeanProperty()) 

più volte. Questo non ha alcun effetto. Inoltre, method.getDeclaringClass().cast(node) non è necessario. L'API di riflessione fa il cast per te.

+3

Come nota di performance, sul mio test di unità faccio 1.000.000.000 di chiamate a metodi (sia metodi aggiunti che proxati), con la riflessione ho avuto quasi 10 secondi di tempo di esecuzione, con la soluzione che avevo circa 9 ms di tempo di esecuzione, bene fatto! – Teg

+0

Grande, grazie per il feedback. Mi chiedo quale versione di Java stai usando. La riflessione è migliorata molto rispetto alle ultime versioni e ho eseguito tutti i miei test delle prestazioni su una recente VM di Java 8. –

+0

Il test dell'unità viene eseguito con Oracle JDK 1.8.0_45 su Mac OS X, le prestazioni dell'API di reflection sono rilevanti solo in componenti critici come questo, dove le chiamate sono troppe – Teg

Problemi correlati