Значения аннотаций 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);

Нет способа динамически изменять свойства аннотации, как говорили другие. Тем не менее, если вы хотите достичь этого, есть два способа сделать это.

  1. Присвойте выражение свойству в аннотации и обрабатывайте его каждый раз, когда извлекаете аннотацию. В вашем случае ваша аннотация может быть

    @MyInterface(aString = "objectA.doSomething(args1, args2)")

Когда вы читаете это, вы можете обработать строку, вызвать метод и получить значение. Spring делает это с помощью SPEL (язык выражений Spring). Это ресурсоемкий процесс, и циклы ЦП теряются каждый раз, когда мы хотим обработать выражение. Если вы используете spring, вы можете подключить beanPostProcessor, обработать выражение один раз и сохранить результат где-нибудь. (Либо объект глобальных свойств, либо карта, которую можно получить в любом месте).

  1. Это хакерский способ делать то, что мы хотим. Java хранит приватную переменную, которая поддерживает карту аннотаций для класса / поля / метода. Вы можете использовать отражение и получить эту карту. Поэтому, обрабатывая аннотацию в первый раз, мы разрешаем выражение и находим фактическое значение. Затем мы создаем объект аннотации требуемого типа. Мы можем поместить вновь созданную аннотацию с фактическим значением (которое является постоянным) в свойстве аннотации и переопределить фактическую аннотацию в найденной карте.

Способ, которым jdk хранит карту аннотаций, зависит от версии java и ненадежен, поскольку он не предоставляется для использования (он является закрытым).

Вы можете найти справочную реализацию здесь.

https://rationaleemotions.wordpress.com/2016/05/27/changing-annotation-values-at-runtime/

PS: я не пробовал и тестировал второй метод.

Другие вопросы по тегам