在运行时修改类定义的注释字符串参数

Ric*_*nka 54 java reflection

想象一下有一节课:

@Something(someProperty = "some value")
public class Foobar {
    //...
}
Run Code Online (Sandbox Code Playgroud)

哪个已编译(我无法控制源),并且是jvm启动时类路径的一部分.我希望能够在运行时将"某些值"更改为其他内容,这样之后的任何反射都将具有我的新值而不是默认的"某个值".

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

ass*_*ias 40

这段代码或多或少地符合您的要求 - 这是一个简单的概念证明:

  • 正确的实施还需要处理 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();
}
Run Code Online (Sandbox Code Playgroud)

  • 这不适用于java 8.`getDeclaredField("annotations")`似乎不再起作用了 (21认同)
  • 本文将介绍如何在Java 8中操作注释:https://rationaleemotions.wordpress.com/2016/05/27/changing-annotation-values-at-runtime/.在页面中间查找"public class AnnotationHelper". (2认同)

Bal*_*der 37

警告:未在OSX上测试 - 请参阅@Marcel的评论

在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;
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

@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());
}
Run Code Online (Sandbox Code Playgroud)

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

用Java 8测试.

  • @Marcel:在查询类中的`Method`(或任何其他成员)时,您会得到一个副本,但该副本是使用缓存信息初始化的(如果存在)。然而,这个缓存的信息是软引用的,可以随时删除。所以这基本上是一个运气问题,后续的反射查询是否会得到被操纵的注释或会重建原始注释。 (3认同)
  • "memberValues" 是 Java 注释的私有映射,它的成员值对存储在其中 - 即这里注释的任何值都存储为其名称/键及其实际值的对。上面的方法通过将其设置为可访问来使用反射简单地访问此字段,然后用给定的新值替换现有值。 (2认同)