如何使用泛型动态生成Java子类

时间:2017-05-02 20:51:45

标签: java cglib

我有一个类,我想动态生成一个子类,并在运行中添加适当的通用。例如,这是一个我希望扩展的基类。

public class Foo<A> {

     private A attribute;

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

     public A getAttribute() {
          return attribute;
     }
}

我想动态生成这样一个子类,它填充&#34; generic&#34;具有指定类型的值,可以说是“狗”和“狗”。对于这个例子。

public class SubClassOfFoo extends Foo<Dog> {

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

我看过CGLib,但我不知道如何扩展和添加一个&#34; Generic&#34;键入它。我在CGLib中遗漏了什么,或者是否有其他能够使用此功能的库?

3 个答案:

答案 0 :(得分:2)

cglib是一个非常古老的库,是在甚至讨论泛型之前创建的。因此,除非您注册ASM访问者并手动添加签名,否则不支持使用库添加通用签名。但是,如果需要,请不要添加适当的桥接方法。

如果你想创建泛型类,你可以查看Byte Buddy(我创建的,我也随便维护cglib),它采用Java的通用类型系统进行所有操作,透明地添加所有桥,就像 javac 一样。您可以使用Byte Buddy创建示例类,如下所示:

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

您现在可以检查生成的子类的泛型类型:

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

答案 1 :(得分:-1)

  

我想动态生成一个像这样填充的子类   “generic”具有指定类型的值

如果动态生成,则表示在运行时。你不能这样做。

Generics in Java在运行时不存在。它们只是编译时的语法糖。

答案 2 :(得分:-2)

使用Java代码可以实现所有可能的字节码生成。

我对cglib并不熟悉,但这里有一些asm代码可以满足您的要求。

与泛型相关的唯一特殊部分是genericSig()方法,除了原型Type.getInternalName(Foo.class)之外,还为超类定义了“第二个签名”。

这不是一般解决方案,它会在问题中为示例创建字节码。对于一般解决方案,还需要考虑许多其他事项,尤其是桥接方法。

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