sirven - reflection en java
Cómo usar una constante de matriz en una anotación (5)
¿Por qué no hacer una enumeración de los valores de anotación, que son claves para los valores de datos reales que desea?
p.ej
enum InfoKeys
{
A("a"),
B("b"),
AB(new String[] { "a", "b" }),
InfoKeys(Object data) { this.data = data; }
private Object data;
}
@SomeAnnotation (values = InfoKeys.AB)
Esto podría mejorarse para la seguridad de tipo, pero se entiende la idea.
Me gustaría usar constantes para los valores de anotación.
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();
}
Las constantes Info.A
e Info.B
se pueden usar en la anotación pero no en la matriz Info.AB
ya que tiene que ser un inicializador de matriz en este lugar. Los valores de anotación están restringidos a valores que podrían estar incluidos en el código de bytes de una clase. Esto no es posible para la constante de matriz, ya que tiene que construirse cuando se carga la información. ¿Hay alguna solución alternativa a este problema?
Esto se debe a que los elementos de los arreglos pueden cambiarse en tiempo de ejecución ( Info.AB[0] = "c";
) mientras que los valores de anotación son constantes después del tiempo de compilación.
Con esto en mente, alguien inevitablemente se confundirá cuando intente cambiar un elemento de Info.AB
y espere que el valor de la anotación cambie (no lo hará). Y si se permitiera que el valor de la anotación cambiara en el tiempo de ejecución, sería diferente al utilizado en el momento de la compilación. ¡Imagina la confusión entonces!
(Donde la confusión aquí significa que hay un error que alguien puede encontrar y pasar horas depurando).
No, no hay solución.
Si bien no hay manera de pasar una matriz directamente como un valor de parámetro de anotación, existe una manera de obtener un comportamiento similar (dependiendo de cómo planee usar sus anotaciones, esto puede no funcionar en todos los casos de uso).
Aquí hay un ejemplo: digamos que tenemos un hostname
InternetServer
clase y tiene una propiedad de hostname
. Nos gustaría usar la Validación de Java regular para asegurarnos de que ningún objeto tenga un nombre de host "reservado". Podemos (de manera muy elaborada) pasar una matriz de nombres de host reservados a la anotación que maneja la validación del nombre de host.
advertencia: con la Validación de Java, sería más habitual utilizar la "carga útil" para pasar este tipo de datos. Quería que este ejemplo fuera un poco más genérico, así que usé una clase de interfaz personalizada.
// 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;
}
}
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;
}