Unable to change package-info.java annotation using Java 8

I ran into a problem when I need to change package information.

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 him hack, at least he looks like one. But does Java 8 work as expected? How do I change this code that it works with Java 8, then?

Javassist answers are welcome too;)

+6
source share
2 answers

Java 8 has changed how annotations are stored internally. Since you are using an unpleasant hack to view with a hard-coded field name, any Java update can cause your code to crash.

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 the code. A permanent fix is ​​to change the annotation itself, rather than dynamically changing it at run time.

 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]; 
+3
source

I came across a situation where I need to export the namespace attribute of the XmlSchema annotation. Since this requires constant meaning, I turned to thinking and found this post. But it was too hard for me, and since op welcomes Javassist's answers, here is one of them:

 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 example attr.addAnnotation(as); ccFile.addAttribute(attr); cc.toClass(); 
0
source

Source: https://habr.com/ru/post/971672/


All Articles