Adding an annotation to a runtime generated class

2020-04-10 01:32发布

问题:

I'm using Javassist(Java 1.7) to add an annotation to the class ClassA, but i get the exception. What am i doing wrong? The code I tried looks like this:

ClassA.java

public class ClassA
{

}

add method

public static <T> Class<T> addXmlRootAnnotationDynamicly(Class<T> declaredTyp) throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException
{
    //pool creation 
    ClassPool pool = ClassPool.getDefault();
    //extracting the class
    CtClass cc = pool.getCtClass(declaredTyp.getCanonicalName());

    // create the annotation
    ClassFile ccFile = cc.getClassFile();
    ConstPool constpool = ccFile.getConstPool();
    AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
    Annotation annot = new Annotation("javax.xml.bind.annotation.XmlRootElement", constpool);
    attr.addAnnotation(annot);

    // add the annotation to the class
    cc.getClassFile().addAttribute(attr);

    // transform the ctClass to java class
    Class<T> dynamiqueBeanClass = cc.toClass();

    //instanciating the updated class 
    //      T sayHelloBean = dynamiqueBeanClass.newInstance();

    return dynamiqueBeanClass;

}

call

Class<ClassA> addXmlRootAnnotationDynamicly = addXmlRootAnnotationDynamicly(ClassA.class);

Exception

javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "de/it_p/pvlight/share/util/ClassA" at javassist.ClassPool.toClass(ClassPool.java:1099) at javassist.ClassPool.toClass(ClassPool.java:1042) at javassist.ClassPool.toClass(ClassPool.java:1000) at javassist.CtClass.toClass(CtClass.java:1224) at de.it_p.pvlight.share.util.JAXBUtil.addXmlRootAnnotationDynamicly(JAXBUtil.java:107) at de.it_p.pvlight.share.util.JAXBUtilTest.addXmlRootAnnotationDynamicly(JAXBUtilTest.java:60) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.lang.reflect.Method.invoke(Method.java:606) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.lang.reflect.Method.invoke(Method.java:606) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) Caused by: java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "de/it_p/pvlight/share/util/ClassA" at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:800) at java.lang.ClassLoader.defineClass(ClassLoader.java:643) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.lang.reflect.Method.invoke(Method.java:606) at javassist.ClassPool.toClass2(ClassPool.java:1112) at javassist.ClassPool.toClass(ClassPool.java:1093) ... 15 more

回答1:

The root of your problem can be found in your stack trace:

attempted duplicate class definition for name: "de/it_p/pvlight/share/util/ClassA"

Your addXmlRootAnnotationDynamicly method takes a loaded class and redefines this very same class without changing its name. After this redefinition, you attempt to load the altered class one more time. This is however not possible in Java where any ClassLoader can only load a class of a given name one single time.

For this reason, the pool.getCtClass method takes a String instead of a loaded Class and works with CtClasses which are used to describe unloaded Classes. To overcome your problem, you have different choices:

  • Change the signature of your method to addXmlRootAnnotationDynamicly(String) and deliver de.it_p.pvlight.share.util.ClassA as the argument. Make sure that this class is not loaded before you transform it, anywhere in your code. You should therefore run the transformation at your application's startup to make sure that the class is not accidentally loaded before the transformation. Your altered Class is then loaded on cc.toClass().
  • Create a subclass of the argument class (or use interfaces) which uses a random name. The subclass is then type compatible to your argument class but is never loaded.
  • Use the Instrumentation API to redefine your loaded class at runtime.
  • Make sure that the input class and the output class are loaded with different ClassLoaders. (not recommended)