73 votes

Comment utiliser une constante de tableau dans une annotation

Je voudrais utiliser des constantes pour les valeurs d'annotation.

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();
}

Les constantes Info.A et Info.B peut être utilisé dans l'annotation, mais pas le tableau Info.AB qu'elle doit être un initialiseur de tableau dans ce lieu. Les valeurs d'Annotation sont limités à des valeurs qui pourraient être incorporé dans l'octet de code d'une classe. Ce n'est pas possible pour la matrice constante, car il doit être construit lors de l' Info est chargé. Est-il une solution pour ce problème?

64voto

Thomas Jung Points 17692

Non, il n'y a pas de solution de contournement.

16voto

amarillion Points 5863

Pourquoi ne pas transformer les valeurs d’annotation en enum, qui sont des clés pour les valeurs de données réelles souhaitées?

par exemple

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

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

@SomeAnnotation (values = InfoKeys.AB)
 

Cela pourrait être amélioré pour la sécurité de type, mais vous voyez l'idée.

3voto

rfcreader Points 361

Alors qu'il n'y a pas de moyen de passer un tableau directement sous forme d'une annotation valeur de paramètre, il est un moyen efficace d'obtenir un comportement similaire (selon la façon dont vous prévoyez d'utiliser votre annotations, cela peut ne pas fonctionner pour chaque cas d'utilisation).

Voici un exemple-disons que nous avons une classe InternetServer et il a un hostname de la propriété. Nous aimerions utiliser Java ordinaire de Validation pour s'assurer qu'aucun objet a une "réservés" nom d'hôte. On peut (un peu minutieusement) passer un tableau de réservé les noms d'hôtes à l'annotation qui gère la validation de nom de domaine.

mise en garde - avec Java Validation, il serait plus coutume d'utiliser la "charge utile" pour passer à ce type de données. Je voulais que cet exemple être un peu plus générique, j'ai donc utilisé une interface personnalisée de classe.

// 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;
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X