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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1387928/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 11:30:50  来源:igfitidea点击:

How to use an array constant in an annotation

javaannotations

提问by Thomas Jung

I would like to use constants for annotation values.

我想对注释值使用常量。

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

The constants Info.Aand Info.Bcan be used in the annotation but not the array Info.ABas it has to be an array initializer in this place. Annotation values are restricted to values that could be inlined into the byte code of a class. This is not possible for the array constant as it has to be constructed when Infois loaded. Is there a workaround for this problem?

常量Info.AInfo.B可以在注释中使用,但不能在数组中使用,Info.AB因为在这个地方它必须是数组初始值设定项。注释值仅限于可以内联到类的字节码中的值。这对于数组常量是不可能的,因为它必须在Info加载时构造。这个问题有解决方法吗?

采纳答案by Thomas Jung

No, there is no workaround.

不,没有解决方法。

回答by amarillion

Why not make the annotation values an enum, which are keys to the actual data values you want?

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

e.g.

例如

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

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

@SomeAnnotation (values = InfoKeys.AB)

This could be improved for type safety, but you get the idea.

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

回答by cobbzilla

While there is no way to pass an array directlyas an annotation parameter value, there isa way to effectively get similar behavior (depending on how you plan on using your annotations, this may not work for every use case).

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

Here's an example -- let's say we have a class InternetServerand it has a hostnameproperty. We'd like to use regular Java Validation to ensure that no object has a "reserved" hostname. We can (somewhat elaborately) pass an array of reserved hostnames to the annotation that handles hostname validation.

这是一个例子——假设我们有一个类InternetServer并且它有一个hostname属性。我们希望使用常规 Java 验证来确保没有对象具有“保留”主机名。我们可以(有点精心)将一组保留的主机名传递给处理主机名验证的注释。

caveat- with Java Validation, it would be more customary to use the "payload" to pass in this kind of data. I wanted this example to be a bit more generic so I used a custom interface class.

警告 - 使用 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;
    }
}

回答by Shakeel Kadam

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;

}

回答by Michael Deardeuff

It is because arrays' elements can be changed at runtime (Info.AB[0] = "c";) while the annotation values are constant after compile time.

这是因为数组的元素可以在运行时更改 ( Info.AB[0] = "c";) 而注释值在编译时保持不变。

With that in mind someone will inevitably be confused when they try to change an element of Info.ABand expect the annotation's value to change (it won't). And if the annotation value were allowed to change at runtime it would differ than the one used at compile time. Imagine the confusion then!

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

(Where confusionhere means that there is a bug that someone mayfind and spend hours debugging.)

(这里的混淆意味着有人可能会发现一个错误并花费数小时进行调试。)

回答by J-Alex

As already was mentioned in previous posts, annotation vales are compile-time constants and there is no way to use an array value as a parameter.

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

I solved this problem a bit differently.

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

If you're owning the processing logic, take advantage of it.

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

For example, give an additional parameter to your annotation:

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

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

Use this parameter:

使用这个参数:

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

And this will be a marker to the AnnotationProcessor, which can do anything - initialize it with an array, use String[], or use Enumslike Enum.values()and map them to String[].

这将是 的标记AnnotationProcessor,它可以做任何事情 - 用数组初始化它,使用String[],或使用EnumslikeEnum.values()并将它们映射到String[].

Hope this will guide someone who has the similar situation in the right direction.

希望这会引导有类似情况的人朝着正确的方向前进。