Значения аннотаций Java, предоставляемые динамически
Я хочу предоставить аннотации с некоторыми значениями, сгенерированными некоторыми методами.
Я пробовал это до сих пор:
public @interface MyInterface {
String aString();
}
@MyInterface(aString = MyClass.GENERIC_GENERATED_NAME)
public class MyClass {
static final String GENERIC_GENERATED_NAME = MyClass.generateName(MyClass.class);
public static final String generateName(final Class<?> c) {
return c.getClass().getName();
}
}
Мысль GENERIC_GENERATED_NAME
равна static final
, она жалуется, что
Значение атрибута аннотации MyInterface.aString
должно быть постоянным выражением
Итак, как достичь этого?
Ответы
Ответ 1
Невозможно динамически генерировать строку, используемую в аннотации. Компилятор оценивает метаданные аннотации для аннотаций RetentionPolicy.RUNTIME
во время компиляции, но GENERIC_GENERATED_NAME
неизвестен до выполнения. И вы не можете использовать сгенерированные значения для аннотаций, которые RetentionPolicy.SOURCE
, потому что они отбрасываются после времени компиляции, поэтому эти сгенерированные значения никогда не будут известны.
Ответ 2
Решение состоит в том, чтобы вместо этого использовать аннотированный метод. Вызовите этот метод (с отражением), чтобы получить динамическое значение.
С точки зрения пользователя у нас будет:
@MyInterface
public class MyClass {
@MyName
public String generateName() {
return MyClass.class.getName();
}
}
Сама аннотация будет определяться как
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface @MyName {
}
Реализация поиска обеих этих аннотаций довольно прямолинейна.
// as looked up by @MyInterface
Class<?> clazz;
Method[] methods = clazz.getDeclaredMethods();
if (methods.length != 1) {
// error
}
Method method = methods[0];
if (!method.isAnnotationPresent(MyName.class)) {
// error as well
}
// This works if the class has a public empty constructor
// (otherwise, get constructor & use setAccessible(true))
Object instance = clazz.newInstance();
// the dynamic value is here:
String name = (String) method.invoke(instance);
Ответ 3
Невозможно модифицировать свойства аннотации динамически, как говорят другие. Тем не менее, если вы хотите достичь этого, есть два способа сделать это.
-
Назначьте выражение для свойства в аннотации и обработать это выражение всякий раз, когда вы извлекаете аннотацию. В вашем случае ваша аннотация может быть
@MyInterface (aString = "objectA.doSomething(args1, args2)" )
Когда вы прочтете это, вы можете обработать строку и вызвать вызов метода и получить значение. Spring делает это с помощью языка выражений SPEL (Spring). Это ресурсоемкий процесс, и циклы процессора теряются каждый раз, когда мы хотим обработать выражение. Если вы используете spring, вы можете подключиться к beanPostProcessor и обработать выражение один раз и сохранить результат где-нибудь. (Либо глобальный объект свойств, либо карту, которую можно найти в любом месте).
- Это хакерский способ делать то, что мы хотим. Java хранит приватную переменную, которая поддерживает карту аннотаций в классе/поле/методе. Вы можете использовать отражение и овладеть этой картой. Поэтому при обработке аннотации в первый раз мы разрешаем выражение и находим фактическое значение. Затем мы создаем объект аннотации требуемого типа. Мы можем поместить вновь созданную аннотацию с фактическим значением (которое является постоянным) на свойство аннотации и переопределить фактическую аннотацию в полученной карте.
Способ хранения jdk карты аннотации зависит от версии java и не является надежным, поскольку он не предназначен для использования (он является закрытым).
Здесь вы можете найти ссылочную реализацию.
https://rationaleemotions.wordpress.com/2016/05/27/changing-annotation-values-at-runtime/
P.S: Я не пробовал и не тестировал второй метод.