Вопрос-ответ

Modify a class definition's annotation string parameter at runtime

Изменение параметра строки аннотации определения класса во время выполнения

Представьте, что существует класс:

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

Который уже скомпилирован (я не могу контролировать исходный код) и является частью пути к классу при запуске jvm. Я хотел бы иметь возможность изменять "некоторое значение" на что-то другое во время выполнения, чтобы любое последующее отражение имело мое новое значение вместо значения по умолчанию "некоторое значение".

Возможно ли это? Если да, то каким образом?

Переведено автоматически
Ответ 1

Предупреждение: не тестировалось в 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.

Ответ 2

Этот код выполняет более или менее то, что вы просите - это простое подтверждение концепции:


  • правильная реализация должна также учитывать 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();
}
Ответ 3

Этот параметр работает на моей машине с 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);
Ответ 4

SPRING может выполнить эту работу очень легко, это может быть полезно разработчику spring . выполните следующие действия :-

Первое решение:- 1) создайте компонент, возвращающий значение для SomeProperty . Здесь я ввел значение SomeProperty с аннотацией @Value из базы данных или файла свойств :-

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

@Bean
public String somePropertyValue(){
return somePropertyValue;
}

2)После этого можно ввести значение somePropertyValue в аннотацию @Something следующим образом :-

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

Второе решение :-

1) создайте параметр getter setter в bean :-

 @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 reflection