Значения аннотаций 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
должно быть постоянным выражением
Так как этого добиться?
3 ответа
Нет способа динамически генерировать строку, используемую в аннотации. Компилятор оценивает метаданные аннотации для RetentionPolicy.RUNTIME
аннотации во время компиляции, но GENERIC_GENERATED_NAME
не известно до времени выполнения. И вы не можете использовать сгенерированные значения для аннотаций, которые RetentionPolicy.SOURCE
потому что они отбрасываются после времени компиляции, поэтому эти сгенерированные значения никогда не будут известны.
Решение состоит в том, чтобы использовать аннотированный метод вместо этого. Вызовите этот метод (с отражением), чтобы получить динамическое значение.
С точки зрения пользователя у нас будет:
@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);
Нет способа динамически изменять свойства аннотации, как говорили другие. Тем не менее, если вы хотите достичь этого, есть два способа сделать это.
Присвойте выражение свойству в аннотации и обрабатывайте его каждый раз, когда извлекаете аннотацию. В вашем случае ваша аннотация может быть
@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/
PS: я не пробовал и тестировал второй метод.