Значения аннотаций 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 и обработать выражение один раз и сохранить результат где-нибудь. (Либо глобальный объект свойств, либо карту, которую можно найти в любом месте).

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

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

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

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

P.S: Я не пробовал и не тестировал второй метод.