2016-01-04 8 views
9

Das Problem besteht darin, eine dynamisch erweiterte Version vorhandener Objekte zu erstellen.Wie implementiert man einen Wrapper Decorator in Java?

Ich kann die Class des Objekts nicht ändern. Stattdessen habe ich zu:

  • Unterklasse es
  • wickeln das vorhandene Objekt in der neuen Class
  • delegieren alle die ursprüngliche Methode zur umschlungenen Objekt aufruft
  • alle Methoden implementieren, die durch eine andere Schnittstelle definiert sind

Die Schnittstelle zu bestehenden Objekten hinzuzufügen ist:

public interface EnhancedNode { 

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

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

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

} 

Mit Byte Buddy gelang es mir, Unterklasse und meine Schnittstelle zu implementieren. Das Problem ist die Delegierung an das umschlossene Objekt. Der einzige Weg, dies zu tun, den ich gefunden habe, ist die Verwendung von Reflektion, was zu langsam ist (ich habe eine starke Last auf die Anwendung und die Leistung ist kritisch).

Bisher ist mein Code:

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

wo Node das Objekt zu Unterklasse/Wrap ist. Die NodeInterceptor leitet die aufgerufenen Methoden an die getNode-Eigenschaft weiter.

Hier ist der Code des 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; 
    } 
} 

alles funktioniert, aber die Schnittverfahren ist zu langsam, ich plane ASM zu verwenden, um direkt die Implementierung jeder Methode der Knoten hinzuzufügen, aber ich hoffe, es ist ein einfacher Weg mit Byte Buddy.

+0

Ich kenne Byte-Buddy nicht, aber ich sehe eine Chance, dass eine neue Proxy-Klasse immer wieder erstellt wird. Gibt es eine Art Caching? – JimmyB

+0

Btw, wie erzwingst du, dass die Klasse des gegebenen Knotens einen öffentlichen Standardkonstruktor hat? – JimmyB

+0

Sie haben eine Schnittstelle, Sie fügen Felder und Methoden hinzu ... Warum können Sie nicht einfach ohne diese dynamische Erweiterung eine Wrapper-Klasse erstellen? – AdamSkywalker

Antwort

5

Sie wollen wahrscheinlich eine Pipe anstatt die Reflection-API verwenden:

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

Um ein Rohr zu verwenden, müssen Sie zuerst installieren müssen. Wenn Sie Java 8 verfügbar haben, können Sie java.util.Function verwenden. Andernfalls definieren Sie einfach einen Typ:

Sie selbst. Der Name des Typs und der Methode sind irrelevant. Die installieren Sie den Typ:

Sind Sie jedoch sicher, dass der Reflexionsteil der kritische Punkt der Leistungsprobleme Ihrer Anwendung ist? Speichert man die generierten Klassen korrekt und arbeitet der Cache effizient? Die Reflektions-API ist schneller als ihre Reputation, insbesondere da Byte Buddy dazu neigt, monomorphe Call-Sites zu implizieren.

Schließlich einige allgemeine Rückmeldungen. Sie rufen

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

mehrere Male. Dies hat keine Auswirkung. Außerdem ist method.getDeclaringClass().cast(node) nicht erforderlich. Die Reflection-API übernimmt die Besetzung für Sie.

+3

Als Leistungsbemerkung, ich auf meinem Unit-Test 1.000.000.000 Aufrufe von Methoden (sowohl addierte und proxed Methoden), mit der Reflexion hatte ich fast 10 Sekunden Ausführungszeit, mit Ihrer Lösung hatte ich etwa 9 ms Ausführungszeit, gut erledigt! – Teg

+0

Großartig, danke für die Rückmeldung. Ich frage mich, welche Java-Version Sie ausführen. Reflection hat sich in den letzten Versionen stark verbessert und ich habe alle meine Leistungstests auf einer Java VM 8 durchgeführt. –

+0

Der Unit-Test läuft mit einem Oracle JDK 1.8.0_45 unter Mac OS X, die Performance der Reflection-API ist nur in kritischen Komponenten wie diesem relevant, wo die Aufrufe zu viele sind – Teg