如何在注释中使用数组常量

问题描述 投票:0回答:7

我想使用常量作为注释值。

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
时构造。这个问题有解决办法吗?

java annotations
7个回答
64
投票

不,没有解决方法。


16
投票

为什么不将注释值设为枚举,这是您想要的实际数据值的键?

例如

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

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

@SomeAnnotation (values = InfoKeys.AB)

这可以改进类型安全,但你明白了。


6
投票

这是因为数组的元素可以在运行时更改(

Info.AB[0] = "c";
),而注释值在编译后保持不变。

考虑到这一点,当有人尝试更改

Info.AB
的元素并期望注释的值改变(它不会)时,他们不可避免地会感到困惑。如果允许在运行时更改注释值,它将与编译时使用的值不同。想象一下当时的混乱吧!

(这里的混乱意味着有人可能发现并花费数小时调试的错误。)


3
投票

虽然没有办法直接传递数组作为注释参数值,但有一种方法可以有效地获得类似的行为(取决于您计划如何使用注释,这可能并不适用于每个用例) .

这是一个例子——假设我们有一个类

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

0
投票
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;

}

0
投票

正如之前的文章中已经提到的,注释值是编译时常量,无法使用数组值作为参数。

我以不同的方式解决了这个问题。

如果您拥有处理逻辑,请利用它。

例如,为您的注释提供一个附加参数:

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

使用此参数:

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

这将是

AnnotationProcessor
的标记,它可以执行任何操作 - 使用数组初始化它,使用
String[]
,或像
Enums
一样使用
Enum.values()
并将它们映射到
String[]

希望这能指导有类似情况的人走向正确的方向。


0
投票

例如,您可以使用

{}
代替
new String[0]

最后会是这样的结果:

@interface SomeAnnotation {
    String[] values() default {};
}
© www.soinside.com 2019 - 2024. All rights reserved.