【问题标题】:How to use an array constant in an annotation如何在注解中使用数组常量
【发布时间】:2010-11-26 03:40:41
【问题描述】:

我想为注释值使用常量。

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.AInfo.B 可以在注释中使用,但不能在数组Info.AB 中使用,因为它必须是一个数组初始化器。注释值仅限于可以内联到类的字节码中的值。这对于数组常量是不可能的,因为它必须在加载 Info 时构造。这个问题有解决办法吗?

【问题讨论】:

  • Eclipse 编译错误非常明确:“注释属性 Client.doesNotWork.values 的值必须是数组初始值设定项”。这很清楚,我认为没有解决方法。

标签: java annotations


【解决方案1】:

正如之前的帖子中已经提到的,注释值是编译时常量,没有办法使用数组值作为参数。

我解决了这个问题有点不同。

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

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

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

使用这个参数:

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

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

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

【讨论】:

  • 你能展示我们如何使用它来将一组值传递给注释吗?
  • 这如何解决OP问题?您仍然需要将数组初始化器传递给注释。如何使用类似常量的结构来集中重复数据?
  • 嗯,你没有澄清上下文。你问了我一个直接的问题,我回答了。如果您仍然想要解决 OP 问题,我建议在注释处理器中使用常量。例如。你有一个enum,如果defaultInittrue,那么在注解处理器中取enum.values()。如果没有太多,您还可以在值数组和参数之间创建映射。
  • 好的,抱歉,澄清一下,我评论的上下文是您要回答的问题。
【解决方案2】:

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

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

(这里的 confusion 表示有人可能发现并花费数小时调试的错误。)

【讨论】:

    【解决方案3】:
    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;
    
    }
    

    【讨论】:

    • 这是一个质量很差的答案,即使它是正确的。添加解释会大大改善它(正如@Peanut所说)
    【解决方案4】:

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

    这是一个例子——假设我们有一个类InternetServer,它有一个hostname 属性。我们想使用常规的 Java 验证来确保没有对象具有“保留”主机名。我们可以(稍微复杂地)将一组保留的主机名传递给处理主机名验证的注解。

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

    【讨论】:

      【解决方案5】:

      不,没有解决方法。

      【讨论】:

      • @JensSchauder 注释是在编译时处理的,所以甚至在代码运行之前。所以数组AB还不存在。
      • 如果编译器希望将“Array Initializer”传递给 Annotation,则应像 private static final String[] AB = { ... }; 那样声明编译时常量。据了解,Annotation 处理发生在实际编译之前,但随后错误信息不准确。
      【解决方案6】:

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

      例如

      enum InfoKeys
      {
       A("a"),
       B("b"),
       AB(new String[] { "a", "b" }),
      
       InfoKeys(Object data) { this.data = data; }
       private Object data;
      }
      
      @SomeAnnotation (values = InfoKeys.AB)
      

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

      【讨论】:

      • +1 不错的想法。编译的示例会更好;-)
      • 好主意。如果您能够更改注释,这没关系。您必须使用 @interface SomeAnnotation { InfoKeys values(); }。遗憾的是,它本身无法更改注释类型。
      • 更改注释类型将限制使用此枚举的值。这是为了限制大多数用例。
      • @Thomas:是的,这种方法也有缺点。这真的取决于你想要达到的目标。
      • 你有一个给定的注释。这个注解的使用不应该是多余的。理想的解决方案是支持“注释实例”的全部和部分重用。完全重用:@x = @SomeAnnotation(...); @x m(); @xy();。部分重用:@SomeAnnotation(childAnnotation=@x) m()。引用注释值是一种妥协,而不是目标。
      猜你喜欢
      • 2021-01-18
      • 1970-01-01
      • 2013-09-09
      • 2014-12-09
      • 2017-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多