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



假设有一个类:

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

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

这可能吗?如果有,怎么做?

警告:未在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;
}

使用例子:

@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());
}

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

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

  • 正确的实现还需要处理declaredAnnotations
  • 如果Class.java中注释的实现发生变化,代码将会中断(即它可能在未来的任何时候中断)
  • 我不知道是否有副作用…
输出:

oldAnnotation = some value
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();
}

这个在我的机器上使用Java 8。将注释@JsonIgnoreProperties(ignoreUnknown = true)ignoreUnknown的值由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);

SPRING可以很容易地完成这项工作,可能对SPRING开发人员有用。遵循以下步骤:-

第一个解:-1)创建一个返回someProperty值的Bean。在这里,我注入了somePropertyValue与@Value注释从DB或属性文件:-

    @Value("${config.somePropertyValue}")
    private String somePropertyValue;
    @Bean
    public String somePropertyValue(){
        return somePropertyValue;
    }
2)在此之后,可以像这样将somePropertyValue注入@Something注释:-
@Something(someProperty = "#{@somePropertyValue}")
public class Foobar {
    //...
}

第二个解:-

1)在bean中创建getter:-

 @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 {
    //...
}

在Java 8中尝试此解决方案

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();
}

我可以在jdk1.8中以这种方式访问和修改注释,但不确定为什么没有效果,

try {
    Field annotationDataField = myObject.getClass().getClass().getDeclaredField("annotationData");
    annotationDataField.setAccessible(true);
    Field annotationsField = annotationDataField.get(myObject.getClass()).getClass().getDeclaredField("annotations");
    annotationsField.setAccessible(true);
    Map<Class<? extends Annotation>, Annotation> annotations =  (Map<Class<? extends Annotation>, Annotation>) annotationsField.get(annotationDataField.get(myObject.getClass()));
    annotations.put(Something.class, newSomethingValue);
} catch (IllegalArgumentException | IllegalAccessException e) {
    e.printStackTrace();
} catch (NoSuchFieldException e) {
    e.printStackTrace();
} catch (SecurityException e) {    
    e.printStackTrace();
}

注释属性值必须是常量—所以除非您想进行一些严肃的字节代码操作,否则这是不可能的。有没有一种更简洁的方法,比如用您想要的注释创建一个包装器类?

相关内容

  • 没有找到相关文章

最新更新