Unable to modify annotation of package

I am facing a problem where I have to modify a package-info.

package-info.java

@javax.xml.bind.annotation.XmlSchema(namespace = "http://some.url/soap/style/document_literal")
package org.example.wsdl.wsdl;

The following code works fine with 1.7.0_45.

//          do not load any classes before, this could break the following code.
            Class<?> pkgInfo = Class.forName("org.example.wsdl.package-info", true, NameSpaceModifier.class.getClassLoader());
            Field field = Class.class.getDeclaredField("annotations");
            field.setAccessible(true);

            final XmlSchema oldAnnotation = (XmlSchema) pkgInfo.getAnnotations()[0];
            logger.debug("Old Annotation namespace value was: " + oldAnnotation.namespace());
            XmlSchema newAnnotation = new XmlSchema() {

                @Override
                public XmlNs[] xmlns() {
                    return oldAnnotation.xmlns();
                }

                @Override
                public String namespace() {
                    return "newNs";
                }

                @Override
                public XmlNsForm elementFormDefault() {
                    return oldAnnotation.elementFormDefault();
                }

                @Override
                public XmlNsForm attributeFormDefault() {
                    return oldAnnotation.attributeFormDefault();
                }

                @Override
                public String location() {
                    return oldAnnotation.location();
                }

                @Override
                public Class<? extends Annotation> annotationType() {
                    return oldAnnotation.annotationType();
                }
            };

            @SuppressWarnings("unchecked")
            Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) field.get(pkgInfo);
            annotations.put(XmlSchema.class, newAnnotation);

            XmlSchema modifiedAnnotation = (XmlSchema) pkgInfo.getAnnotations()[0];

When compiling and executing the same Code with 1.8.0_05 I get this error message:

java.lang.NoSuchFieldException: annotations
    at java.lang.Class.getDeclaredField(Class.java:2057)

I know its a Hack, at least it looks like one. But does Java 8 work here as expected? How do I have to change that code that it does work with Java 8 then?

Javassist answers are welcome too ;)


Java 8 changed how annotations are stored internally. Since you are using a nasty reflection hack with a hardcoded field name, any Java update has the potential to rebreak your code.

java.lang.Class :

/**
 * @since 1.5
 */
public Annotation[] getAnnotations() {
    return AnnotationParser.toArray(annotationData().annotations);
}

private volatile transient AnnotationData annotationData;

private AnnotationData annotationData() {
    while (true) { // retry loop
        AnnotationData annotationData = this.annotationData;
        int classRedefinedCount = this.classRedefinedCount;
        if (annotationData != null &&
            annotationData.redefinedCount == classRedefinedCount) {
            return annotationData;
        }
        // null or stale annotationData -> optimistically create new instance
        AnnotationData newAnnotationData = createAnnotationData(classRedefinedCount);
        // try to install it
        if (Atomic.casAnnotationData(this, annotationData, newAnnotationData)) {
            // successfully installed new AnnotationData
            return newAnnotationData;
        }
    }
}

private static class AnnotationData {
    final Map<Class<? extends Annotation>, Annotation> annotations;
    final Map<Class<? extends Annotation>, Annotation> declaredAnnotations;

    // Value of classRedefinedCount when we created this AnnotationData instance
    final int redefinedCount;

    AnnotationData(Map<Class<? extends Annotation>, Annotation> annotations,
                   Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
                   int redefinedCount) {
        this.annotations = annotations;
        this.declaredAnnotations = declaredAnnotations;
        this.redefinedCount = redefinedCount;
    }
}

I suppose you could use the new field values to temporarily fix your code. A permanent fix is to change the annotation itself, instead of dynamically modifying it at runtime.

Field annotationDataField = Class.class.getDeclaredField("annotationData");
annotationDataField.setAccessible(true);

Object annotationData = annotationDataField.get(pkgInfo);

Field annotationsField = annotationData.getClass().getDeclaredField("annotations");
annotationsField.setAccessible(true);

Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) annotationsField
        .get(annotationData);
annotations.put(XmlSchema.class, newAnnotation);

XmlSchema modifiedAnnotation = (XmlSchema) pkgInfo.getAnnotations()[0];

I encountered a situation where I need to externalize the namespace attribute of a XmlSchema annotation. As it needs a constant value, I turned myself to reflection and found this post. But it was too hackish for me and as the op welcomes Javassist answers too, here's one :

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("org.example.wsdl.package-info");     
ClassFile ccFile = cc.getClassFile();
ConstPool cp = ccFile.getConstPool();

AnnotationsAttribute attr = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);
Annotation as = new Annotation("javax.xml.bind.annotation.XmlSchema", cp);
as.addMemberValue("namespace", new StringMemberValue("newNs", cp));
EnumMemberValue emv = new EnumMemberValue(cp);
emv.setType("javax.xml.bind.annotation.XmlNsForm");
emv.setValue("QUALIFIED");
as.addMemberValue("elementFormDefault", emv);
// Others member values can be added manually or cloned from the original annotation like in the op's example
attr.addAnnotation(as);
ccFile.addAttribute(attr);
cc.toClass();
链接地址: http://www.djcxy.com/p/80324.html

上一篇: 查询以确定哪些表正在被抽真空?

下一篇: 无法修改包的注释