2017-05-02 11 views
0

Ich habe eine Klasse, die ich eine Unterklasse dynamisch generieren und die richtige generische on-the-fly hinzufügen möchte. Zum Beispiel, hier ist eine Basisklasse, die ich erweitern möchte.Wie man eine Java-Unterklasse mit Generics dynamisch erzeugt

public class Foo<A> { 

    private A attribute; 

    // constructor 
    public Foo(A value) { 
     this.attribute = value; 
    } 

    public A getAttribute() { 
      return attribute; 
    } 
} 

Ich möchte dynamisch eine Unterklasse wie diese erzeugen, die mit einer bestimmten Art in der „generic“ A-Wert füllt, sagen wir mal ‚Dog‘ für dieses Beispiel.

public class SubClassOfFoo extends Foo<Dog> { 

     public SubClassOfFoo(Dog dog) { 
       super(dog); 
     } 
} 

Ich habe CGLib angeschaut, aber ich sehe nicht, wie man einen "generischen" Typ erweitert und hinzufügt. Fehle ich etwas in CGLib oder gibt es eine andere Bibliothek mit dieser Funktion?

+0

Haben Sie Quellcode gene wollen, oder Klassen zur Laufzeit? – NickL

+0

Ich würde es vorziehen, Klassen zur Laufzeit on-the-fly zu erstellen. Wenn ich Quellcode generieren und dann die Quelle zur Laufzeit laden muss, ist das eine zweitbeste Option ... – Jason

+0

Nun ... der 'generische A'-Typ wird zur Laufzeit aufgrund des Typ-Löschens vom Typ 'Object' sein. – NickL

Antwort

-1

Ich mag eine Unterklasse wie diese dynamisch erzeugen, die dynamisch Sie zur Laufzeit bedeuten erzeugen

Wenn für mit einer bestimmten Art in dem „generic“ A-Wert erfüllt. Du kannst es nicht tun.

Generics in Java sind zur Laufzeit nicht vorhanden. Sie sind einfach kompilierbarer syntaktischer Zucker.

+0

Ich denke, es gibt Byte-Code-Schreibbibliotheken, die dies tun.Ich denke, einige JEE-Container tun dies für die Implementierung bestimmter Funktionen, z. B. Entitäten für JPA. Aber ich denke nicht, dass Sie es mit reinem Java machen können (Sie benötigen JNI-Code) und es ist sicher nicht so einfach. – markspace

+0

Typ löschen entfernt nur Typinformationen von "normalen" Instanzen, nicht von Referenzen oder Klassenobjekten. –

+0

@markspace Ich weiß, dass es mit einigen Bytecode Manipulation Framework als ASM getan werden kann. Aber wenn er auf diese Weise Klassen generiert (sagen wir ASM). Warum braucht er dann Generika? Was ich hervorhebe ist, dass Generic nur Kompilierzeit ist. – granmirupa

-2

Mit der Bytecode-Generierung ist alles möglich, was mit Java-Code möglich ist.

Ich bin nicht so vertraut mit Cglib, aber hier ist ein Asm-Code, der tun wird, was Sie gefragt haben.

Der einzige spezielle Teil bezogen auf Generics es die genericSig() Methode, die "eine zweite Signatur" für die Oberklasse neben der Rohform Type.getInternalName(Foo.class) definiert.

Dies ist keine allgemeine Lösung, es erstellt den Bytecode für das Beispiel in der Frage. Für die allgemeine Lösung müssen viele andere Dinge berücksichtigt werden, insbesondere Brückenmethoden.

import org.objectweb.asm.ClassWriter; 
import org.objectweb.asm.MethodVisitor; 
import org.objectweb.asm.Opcodes; 
import org.objectweb.asm.Type; 
import org.objectweb.asm.signature.SignatureVisitor; 
import org.objectweb.asm.signature.SignatureWriter; 



public class GenExtendFoo { 
    public static void main(String[] args) throws Exception { 
     ClassWriter cw = new ClassWriter(0); 
     /** Extend Foo with normal raw superclass "com/Foo" 
      And generic superclass genericSig() Lcom/Foo<Ljava/lang/Integer;>; 
     **/ 
     cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "SubClass", genericSig(Foo.class, Integer.class), 
      Type.getInternalName(Foo.class), new String[] {}); 
     createConstructor(cw); 
     cw.visitEnd(); 

     byte[] b = cw.toByteArray(); 
     Class<?> cls = (Class<?>) new MyClassLoader().defineClass("SubClass", b); 

     Foo<Integer> instance = (Foo<Integer>) cls.getConstructor(Integer.class).newInstance(1); 
     System.out.println(instance.getValue()); 

     /* The generic type is available with GenericSuperclass just like in the plain java version */ 
     ParameterizedType para = (ParameterizedType) instance.getClass().getGenericSuperclass(); 
     System.out.println(para.getActualTypeArguments()[0]); 
    } 

    private static void createConstructor(ClassWriter cw) { 
     // Create constructor with one parameter that calls superclass 
     // constructor with one parameter 
     MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", 
      "(L" + Type.getInternalName(Integer.class) + ";)V", null, null); 
     mv.visitMaxs(2, 2); 
     mv.visitVarInsn(Opcodes.ALOAD, 0); 
     mv.visitVarInsn(Opcodes.ALOAD, 1); 
     mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Foo.class), "<init>", 
      "(L" + Type.getInternalName(Object.class) + ";)V", false); // call 
     mv.visitInsn(Opcodes.RETURN); 

     mv.visitEnd(); 
    } 

    public static String genericSig(Class<?> mainType, Class<?> typeParameter) { 
     SignatureVisitor sv = new SignatureWriter(); 
     SignatureVisitor psv = sv.visitSuperclass(); 
     psv.visitClassType(Type.getInternalName(mainType)); 
     SignatureVisitor ppsv = psv.visitTypeArgument('='); 
     ppsv.visitClassType(Type.getInternalName(typeParameter)); 
     ppsv.visitEnd(); 
     psv.visitEnd(); 
     return sv.toString(); 
    } 
} 
static class MyClassLoader extends ClassLoader { 
    public Class<?> defineClass(String name, byte[] b) { 
     return defineClass(name, b, 0, b.length); 
    } 
} 
+0

Warum wird dies abgelehnt (-2)? Wenn jemand kommentieren könnte, warum es abgelehnt wird, werde ich nicht meine Zeit damit verschwenden, dieses Beispiel als Antwort zu verfolgen. Vielen Dank. – Jason

+0

@Jason kann man nur raten. Aber eine Sache mit Sicherheit, ich hätte erwähnen sollen, dass diese "Lösung" für Ihr angegebenes Problem geschrieben ist und weit von einer allgemeinen Lösung entfernt ist. Abhängig von der Komplexität der erweiterten Klasse müssen Sie möglicherweise Brückenmethoden einführen. –

1

cglib ist eine sehr alte Bibliothek und erstellt wurde, bevor Generika noch diskutiert. Daher wird das Hinzufügen generischer Signaturen mit der Bibliothek nicht unterstützt, es sei denn, Sie registrieren einen ASM-Besucher und fügen die Signatur manuell hinzu. Dies fügt jedoch nicht die geeigneten Bridge-Methoden hinzu, wenn Sie diese benötigen.

Wenn Sie generische Klassen erstellen möchten, können Sie einen Blick auf Byte Buddy haben (was ich geschaffen, habe ich auch nebenbei halten cglib), die für alle ihre Operationen Java generische Typ System nimmt und fügt transparent alle Brücken ebenso wie Javac würde tun. Sie können die Beispielklasse mit Byte Buddy wie folgt erstellen:

Class<?> subclass = new ByteBuddy() 
    .subclass(TypeDescription.Generic.Builder.parameterizedType(Foo.class, Dog.class) 
              .build()) 
    .make() 
    .load(Foo.class.getClassLoader()) 
    .getLoaded(); 

können Sie nun den generischen Typ der erzeugten Unterklasse überprüfen:

Type type = subclass.getGenericSuperclass(); 
assert type instanceof ParameterizedType; 
assert ((ParameterizedType) type)).getRawType() == Foo.class; 
assert ((ParameterizedType) type).getActualTypeArguments()[0] == Dog.class; 
Verwandte Themen