[英]How to use an array constant in an 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();
}
常量Info.A
和Info.B
可以在注释中使用,但不能在数组Info.AB
中使用,因为它必须是这里的数组初始值设定项。 注释值仅限于可以内联到类的字节码中的值。 这对于数组常量是不可能的,因为它必须在加载Info
时构建。 有解决此问题的方法吗?
不,没有解决方法。
为什么不将注释值设为枚举,它是您想要的实际数据值的键?
例如
enum InfoKeys
{
A("a"),
B("b"),
AB(new String[] { "a", "b" }),
InfoKeys(Object data) { this.data = data; }
private Object data;
}
@SomeAnnotation (values = InfoKeys.AB)
这可以改进类型安全,但你明白了。
这是因为数组的元素可以在运行时更改( Info.AB[0] = "c";
),而注释值在编译后保持不变。
考虑到这一点,当某人尝试更改Info.AB
的元素并期望注释的值会更改(不会)时,他们将不可避免地感到困惑。 如果允许在运行时更改注释值,它将不同于编译时使用的值。 想象一下当时的混乱!
(这里的混淆意味着有人可能会发现并花费数小时调试的错误。)
虽然无法将数组直接作为注释参数值传递,但有一种方法可以有效地获得类似的行为(取决于您计划如何使用注释,这可能不适用于每个用例)。
这是一个例子——假设我们有一个InternetServer
类,它有一个hostname
属性。 我们想使用常规的 Java 验证来确保没有对象具有“保留”主机名。 我们可以(稍微复杂地)将一组保留的主机名传递给处理主机名验证的注解。
警告 - 使用 Java 验证,更习惯使用“有效负载”来传递此类数据。 我希望这个例子更通用一点,所以我使用了一个自定义接口类。
// 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;
}
正如在之前的文章中已经提到的,注释值是编译时常量,没有办法使用数组值作为参数。
我以不同的方式解决了这个问题。
如果您拥有处理逻辑,请利用它。
例如,为您的注释提供一个附加参数:
@Retention(RUNTIME)
@Target(METHOD)
@interface SomeAnnotation {
String[] values();
boolean defaultInit() default false;
}
使用此参数:
@SomeAnnotation(defaultInit = true)
void willWork();
这将是AnnotationProcessor
的标记,它可以做任何事情 - 使用数组初始化它,使用String[]
,或使用Enums
Enum.values()
之类的枚举并将它们映射到String[]
。
希望这将引导有类似情况的人朝着正确的方向前进。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.