在运行时修改类定义的注释string参数

想象一下有一堂课:

@Something(someProperty = "some value") public class Foobar { //... } 

哪些已经编译(我无法控制源),并且是jvm启动时类path的一部分。 我希望能够在运行时将“某些值”更改为其他值,以便之后的任何reflection都将具有我的新值,而不是默认的“某个值”。

这可能吗? 如果是这样,怎么样?

这个代码或多或less地或多或less地要求 – 这是一个简单的概念certificate:

  • 一个适当的实施还需要处理declaredAnnotations
  • 如果Class.java中的注释实现发生更改,代码将会中断(即将来可能会随时中断)
  • 我不知道是否有副作用…

输出:

oldAnnotation =一些值
modifiedAnnotation =另一个值

 public static void main(String[] args) throws Exception { final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0]; System.out.println("oldAnnotation = " + oldAnnotation.someProperty()); Annotation newAnnotation = new Something() { @Override public String someProperty() { return "another value"; } @Override public Class<? extends Annotation> annotationType() { return oldAnnotation.annotationType(); } }; Field field = Class.class.getDeclaredField("annotations"); field.setAccessible(true); Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) field.get(Foobar.class); annotations.put(Something.class, newAnnotation); Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0]; System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty()); } @Something(someProperty = "some value") public static class Foobar { } @Retention(RetentionPolicy.RUNTIME) @interface Something { String someProperty(); } 

警告:未在OSX上testing – 请参阅@Marcel的评论

testing在OSX上。 工作正常。

由于我也需要在运行时更改注释值,所以我重新讨论了这个问题。

这是@assylias方法的一个修改版本(非常感谢灵感)。

 /** * Changes the annotation value for the given key of the given annotation to newValue and returns * the previous value. */ @SuppressWarnings("unchecked") public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue){ Object handler = Proxy.getInvocationHandler(annotation); Field f; try { f = handler.getClass().getDeclaredField("memberValues"); } catch (NoSuchFieldException | SecurityException e) { throw new IllegalStateException(e); } f.setAccessible(true); Map<String, Object> memberValues; try { memberValues = (Map<String, Object>) f.get(handler); } catch (IllegalArgumentException | IllegalAccessException e) { throw new IllegalStateException(e); } Object oldValue = memberValues.get(key); if (oldValue == null || oldValue.getClass() != newValue.getClass()) { throw new IllegalArgumentException(); } memberValues.put(key,newValue); return oldValue; } 

用法示例:

 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ClassAnnotation { String value() default ""; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface FieldAnnotation { String value() default ""; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MethodAnnotation { String value() default ""; } @ClassAnnotation("class test") public static class TestClass{ @FieldAnnotation("field test") public Object field; @MethodAnnotation("method test") public void method(){ } } public static void main(String[] args) throws Exception { final ClassAnnotation classAnnotation = TestClass.class.getAnnotation(ClassAnnotation.class); System.out.println("old ClassAnnotation = " + classAnnotation.value()); changeAnnotationValue(classAnnotation, "value", "another class annotation value"); System.out.println("modified ClassAnnotation = " + classAnnotation.value()); Field field = TestClass.class.getField("field"); final FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class); System.out.println("old FieldAnnotation = " + fieldAnnotation.value()); changeAnnotationValue(fieldAnnotation, "value", "another field annotation value"); System.out.println("modified FieldAnnotation = " + fieldAnnotation.value()); Method method = TestClass.class.getMethod("method"); final MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class); System.out.println("old MethodAnnotation = " + methodAnnotation.value()); changeAnnotationValue(methodAnnotation, "value", "another method annotation value"); System.out.println("modified MethodAnnotation = " + methodAnnotation.value()); } 

这种方法的优点是,不需要创build新的注释实例。 因此不需要事先知道具体的标注类。 另外,由于原始注解实例保持不变,副作用应该是最小的。

testing了Java 8。

这个在我的机器上用Java 8工作。它将注解@JsonIgnoreProperties(ignoreUnknown = true)中的@JsonIgnoreProperties(ignoreUnknown = true)的值从true更改为false

 final List<Annotation> matchedAnnotation = Arrays.stream(SomeClass.class.getAnnotations()).filter(annotation -> annotation.annotationType().equals(JsonIgnoreProperties.class)).collect(Collectors.toList()); final Annotation modifiedAnnotation = new JsonIgnoreProperties() { @Override public Class<? extends Annotation> annotationType() { return matchedAnnotation.get(0).annotationType(); } @Override public String[] value() { return new String[0]; } @Override public boolean ignoreUnknown() { return false; } @Override public boolean allowGetters() { return false; } @Override public boolean allowSetters() { return false; } }; final Method method = Class.class.getDeclaredMethod("getDeclaredAnnotationMap", null); method.setAccessible(true); final Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) method.invoke(SomeClass.class, null); annotations.put(JsonIgnoreProperties.class, modifiedAnnotation); 

试用Java 8的这个解决scheme

 public static void main(String[] args) throws Exception { final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0]; System.out.println("oldAnnotation = " + oldAnnotation.someProperty()); Annotation newAnnotation = new Something() { @Override public String someProperty() { return "another value"; } @Override public Class<? extends Annotation> annotationType() { return oldAnnotation.annotationType(); } }; Method method = Class.class.getDeclaredMethod("annotationData", null); method.setAccessible(true); Object annotationData = method.invoke(getClass(), null); Field declaredAnnotations = annotationData.getClass().getDeclaredField("declaredAnnotations"); declaredAnnotations.setAccessible(true); Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) declaredAnnotations.get(annotationData); annotations.put(Something.class, newAnnotation); Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0]; System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty()); } @Something(someProperty = "some value") public static class Foobar { } @Retention(RetentionPolicy.RUNTIME) @interface Something { String someProperty(); } 

SPRING可以很容易地完成这项工作,对春季开发者可能有用。 按着这些次序 :-

第一个解决scheme: 1)创build一个返回someProperty值的Bean。 在这里,我从数据库或属性文件注入了@PropertyValue @Value注释: –

  @Value("${config.somePropertyValue}") private String somePropertyValue; @Bean public String somePropertyValue(){ return somePropertyValue; } 

2)在这之后,可以像这样将somePropertyValue注入到@Something注解中: –

 @Something(someProperty = "#{@somePropertyValue}") public class Foobar { //... } 

第二种解决scheme:

1)在bean中创buildgetter setter:

  @Component public class config{ @Value("${config.somePropertyValue}") private String somePropertyValue; public String getSomePropertyValue() { return somePropertyValue; } public void setSomePropertyValue(String somePropertyValue) { this.somePropertyValue = somePropertyValue; } } 

2)在这之后,可以像这样将somePropertyValue注入到@Something注解中: –

 @Something(someProperty = "#{config.somePropertyValue}") public class Foobar { //... } 

注释属性值必须是常量 – 所以除非你想要做一些严肃的字节码操作,否则不可能。 有没有更清晰的方法,比如用你所需要的注释创build一个包装类?