想象有一个类:
@Something(someProperty = "some value")
public class Foobar {
//...
}
它已经编译(我无法控制源代码),并且是 jvm 启动时类路径的一部分。我希望能够在运行时将“某个值”更改为其他值,这样之后的任何反射都将具有我的新值,而不是默认的“某个值”。
这可能吗?如果是这样,怎么做?
想象有一个类:
@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());
}
这种方法的优点是不需要创建新的注释实例。因此不需要事先知道具体的注解类。此外,由于原始注释实例保持不变,因此副作用应该是最小的。
使用 Java 8 测试。
这段代码或多或少地做了你所要求的——它是一个简单的概念证明:
declaredAnnotations
输出:
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();
}
这个适用于我的 Java 8 机器。它将ignoreUnknown
注释中的值@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);
SPRING 可以很容易地完成这项工作,可能对 spring 开发人员有用。跟着这些步骤 :-
第一个解决方案:- 1)创建一个返回 someProperty 值的 Bean。在这里,我从 DB 或属性文件中注入了带有 @Value 注释的 somePropertyValue :-
@Value("${config.somePropertyValue}")
private String somePropertyValue;
@Bean
public String somePropertyValue(){
return somePropertyValue;
}
2)在此之后,可以将 somePropertyValue 注入到 @Something 注释中,如下所示:-
@Something(someProperty = "#{@somePropertyValue}")
public class Foobar {
//...
}
第二种解决方案: -
1)在bean中创建getter 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 {
//...
}
试试这个 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();
}
注释属性值必须是常量——所以除非你想做一些严肃的字节码操作,否则这是不可能的。有没有更简洁的方法,比如用你想要的注释创建一个包装类?