Подтвердить что ты не робот

Как использовать константу массива в аннотации

Я хотел бы использовать константы для значений аннотаций.

interface Client {

    @Retention(RUNTIME)
    @Target(METHOD)
    @interface SomeAnnotation { String[] values(); }

    interface Info {
        String A = "a";
        String B = "b";
        String[] AB = new String[] { A, B };
    }

    @SomeAnnotation(values = { Info.A, Info.B })
    void works();

    @SomeAnnotation(values = Info.AB)
    void doesNotWork();
}

Константы Info.A и Info.B могут использоваться в аннотации, но не в массиве Info.AB, поскольку в этом месте должен быть инициализатор массива. Значения аннотации ограничены значениями, которые могут быть включены в байтовый код класса. Это невозможно для константы массива, поскольку она должна быть построена при загрузке Info. Есть ли способ обхода проблемы?

4b9b3361

Ответ 1

Нет, нет обходного пути.

Ответ 2

Почему бы не сделать значения аннотации enum, которые являются ключами к фактическим значениям данных, которые вы хотите?

например.

enum InfoKeys
{
 A("a"),
 B("b"),
 AB(new String[] { "a", "b" }),

 InfoKeys(Object data) { this.data = data; }
 private Object data;
}

@SomeAnnotation (values = InfoKeys.AB)

Это может быть улучшено для безопасности типов, но вы получаете идею.

Ответ 3

Хотя невозможно передать массив напрямую в качестве значения параметра аннотации, есть способ эффективного получения аналогичного поведения (в зависимости от того, как вы планируете использовать ваши аннотации, это может не работать для каждого варианта использования).

Вот пример - допустим, у нас есть класс InternetServer и он имеет свойство hostname. Мы хотели бы использовать регулярную проверку Java, чтобы гарантировать, что ни один объект не имеет "зарезервированное" имя хоста. Мы можем (несколько тщательно) передать массив зарезервированных имен хостов в аннотацию, которая обрабатывает проверку имени хоста.

caveat- с помощью Java Validation было бы более привычным использовать "полезную нагрузку" для передачи данных такого типа. Я хотел, чтобы этот пример был более общим, поэтому я использовал собственный класс интерфейса.

// InternetServer.java -- an example class that passes an array as an annotation value
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.Pattern;

public class InternetServer {

    // These are reserved names, we don't want anyone naming their InternetServer one of these
    private static final String[] RESERVED_NAMES = {
        "www", "wwws", "http", "https",
    };

    public class ReservedHostnames implements ReservedWords {
        // We return a constant here but could do a DB lookup, some calculation, or whatever
        // and decide what to return at run-time when the annotation is processed.
        // Beware: if this method bombs, you're going to get nasty exceptions that will
        // kill any threads that try to load any code with annotations that reference this.
        @Override public String[] getReservedWords() { return RESERVED_NAMES; }
    }

    @Pattern(regexp = "[A-Za-z0-9]{3,}", message = "error.hostname.invalid")
    @NotReservedWord(reserved=ReservedHostnames.class, message="error.hostname.reserved")
    @Getter @Setter private String hostname;
}

// NotReservedWord.java -- the annotation class
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy=ReservedWordValidator.class)
@Documented
public @interface NotReservedWord {

    Class<? extends ReservedWords> reserved ();

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String message() default "{err.reservedWord}";

}

// ReservedWords.java -- the interface referenced in the annotation class
public interface ReservedWords {
    public String[] getReservedWords ();
}

// ReservedWordValidator.java -- implements the validation logic
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ReservedWordValidator implements ConstraintValidator<NotReservedWord, Object> {

    private Class<? extends ReservedWords> reserved;

    @Override
    public void initialize(NotReservedWord constraintAnnotation) {
        reserved = constraintAnnotation.reserved();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (value == null) return true;
        final String[] words = getReservedWords();
        for (String word : words) {
            if (value.equals(word)) return false;
        }
        return true;
    }

    private Map<Class, String[]> cache = new ConcurrentHashMap<>();

    private String[] getReservedWords() {
        String[] words = cache.get(reserved);
        if (words == null) {
            try {
                words = reserved.newInstance().getReservedWords();
            } catch (Exception e) {
                throw new IllegalStateException("Error instantiating ReservedWords class ("+reserved.getName()+"): "+e, e);
            }
            cache.put(reserved, words);
        }
        return words;
    }
}

Ответ 4

Это потому, что элементы массивов могут быть изменены во время выполнения (Info.AB[0] = "c";), в то время как значения аннотаций остаются неизменными после времени компиляции.

Имея это в виду, кто-то неизбежно будет сбит с толку, когда попытается изменить элемент Info.AB и ожидать, что значение аннотации изменится (этого не произойдет). И если бы значение аннотации можно было изменять во время выполнения, оно бы отличалось от того, которое использовалось во время компиляции. Тогда представьте себе путаницу!

(Где путаница здесь означает, что есть ошибка, которую кто-то может найти и отработать часами.)

Ответ 5

Как уже упоминалось в предыдущих статьях, аннотации vale являются константами времени компиляции, и нет возможности использовать значение массива в качестве параметра.

Я решил эту проблему немного по-другому.

Если вы владеете логикой обработки, воспользуйтесь ею.

Например, добавьте дополнительный параметр к вашей аннотации:

@Retention(RUNTIME)
@Target(METHOD)
@interface SomeAnnotation { 
    String[] values();
    boolean defaultInit() default false;
}

Используйте этот параметр:

@SomeAnnotation(defaultInit = true)
void willWork();

И это будет маркер на AnnotationProcessor, который может сделать что - либо - инициализировать его с массивом, используйте String[], или использовать Enums как Enum.values() и сопоставить их с String[].

Надеюсь, что это направит кого-то, кто имеет подобную ситуацию в правильном направлении.

Ответ 6

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Handler {

    enum MessageType { MESSAGE, OBJECT };

    String value() default "";

    MessageType type() default MessageType.MESSAGE;

}