2016-01-10 6 views
8

Ich möchte eine Implementierung zur Laufzeit für eine abstrakte Klasse mit Byte Buddy erstellen und ich bin mit dem Problem konfrontiert, dass eine java.lang.AbstractMethodError geworfen wird beim Aufrufen einer Methode aus einer erstellten Instanz . Ich habe eine vorhandene abstract Klasse wie folgt (die ich eigentlich nicht ändern und die enthält tatsächlich mehr Logik):Byte-Buddy: Erstellen Sie die Implementierung für eine abstrakte Klasse

public abstract class Algorithm { 
    abstract int execute(); 
} 

die folgenden minimalen Proben verwenden, würde ich meine Algorithm Instanz wie einen konstanten Wert zurückzukehren:

Class<?> type = new ByteBuddy() 
         .subclass(Algorithm.class) 
         .method(ElementMatchers.named("execute")) 
         .intercept(FixedValue.value(42)) 
         .make() 
         .load(classLoader, ClassLoadingStrategy.Default.WRAPPER) 
         .getLoaded(); 
Algorithm instance = (Algorithm) type.newInstance(); 
System.out.println(myInstance.execute()); 

Dies führt jedoch zu folgenden Ausnahme:

Exception in thread "main" java.lang.AbstractMethodError: package.Algorithm.execute()I 

(wenn ich Algorithm experimentell ändern ein interface, alles funktioniert gut, aber das löst nicht mein spezifisches Problem).

Antwort

11

Es könnte Sie überraschen, aber das gleiche würde passieren, wenn Sie den gleichen Code mit javac mit der gleichen Klasse Loader-Setup generiert hätten. Was Sie beobachten, wird durch die Angabe von package-privacy in der JLS impliziert. Ihre nicht-Schnittstelle Klasse

public abstract class Algorithm { 
    abstract int execute(); 
} 

definiert ein Paket-private-Methode. Da Sie keinen benutzerdefinierten Namen für die generierte Klasse definieren, generiert Byte Buddy eine Unterklasse mit einem zufälligen Namen, der sich im selben Paket befindet. Byte Buddy entdeckt die executable Methode als überschreibbar aus der generierten Unterklasse und implementiert sie genau so, wie Sie es erwarten würden.

Sie verwenden jedoch die Strategie ClassLoadingStrategy.Default.WRAPPER, um die Klasse zu laden, die einen neuen Kindklassenlader der einen Lade Algorithm erstellt. In Java, zur Laufzeit, sind jedoch zwei Pakete nur gleich, wenn der Name des Pakets gleich ist und beide Pakete von demselben ClassLoader geladen werden. Die spätere Bedingung trifft für Ihren Fall nicht zu, sodass die JVM keinen Polymorphismus mehr auf die Klasse execute anwendet. Mit dem Aufruf

((Algorithm) type.newInstance()).execute(); 

rufen Sie daher nicht die generierte Methode, sondern die ursprüngliche, abstrakte Methode auf. Daher wird - gemäß der JLS - ein AbstractMethodError geworfen.

Um dieses Problem zu beheben, muss man entweder die erzeugte Klasse im gleichen Paket laden, den Standard INJECTION Strategie, oder Sie haben execute als public definieren (diese implizit ist, wenn eine Schnittstelle zu definieren) oder protected Verfahren derart, dass Es gelten die Regeln für Polymorphie, die Sie erwarten. Als dritte Option können Sie die korrekte Laufzeit-Methode

type.getDeclaredMethod("execute").invoke(type.newInstance()); 
+0

Danke Rafael für Ihre sehr ausführliche Erklärung berufen, als Chance wird es haben, entdeckte ich nur zwei Minuten vor, dass die Wurzel meines Problem verursacht wurde die Tatsache, dass die abstrakte Methode Paket privat war. Die "Injektion" ist meine Lösung. Übrigens, toller Job bei Byte Buddy! – qqilihq