【问题标题】:Use cases for implementing annotations实现注解的用例
【发布时间】:2011-03-21 11:50:51
【问题描述】:

实现注释的有效用例是什么?

在主要设计基于注解的配置系统时,我偶尔需要创建实现代码生成或编程配置的注解的类。

另一种方法是将注释中包含的数据镜像到 DTO 中,这似乎是一种开销。

这是一个例子:

public enum IDType {
    LOCAL,
    URI,
    RESOURCE;
}

@Documented
@Target( { METHOD, FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Id {
    /**
     * @return
     */
    IDType value() default IDType.LOCAL;
}

实现

public class IdImpl implements Id{

    private final IDType idType;

    public IdImpl(IDType idType){
        this.idType = idType;
    }

    @Override
    public IDType value() {
        return idType;
    }

    @Override
    public Class<? extends Annotation> annotationType() {
        return Id.class;
    }

}

我收到了编译器警告,但它似乎是许多用例的有效工具。

上面例子的警告是

注解类型 ID 不应该是 用作 IdImpl 的超级接口

已编辑:

我刚刚从Guice 找到了这个例子:

bind(CreditCardProcessor.class)
    .annotatedWith(Names.named("Checkout"))
    .to(CheckoutCreditCardProcessor.class);

看到这个Javadoc from Names

有没有人知道为什么存在这种限制或有其他一些用例?

【问题讨论】:

  • @djina:是的,每次你不得不说一分钱,你就会变得富有。它总是让我感到惊讶。
  • 也许只有我,但似乎与这个问题密切相关:stackoverflow.com/questions/1624084/…
  • @j-flemm,注解子类型是另一回事。注释子类型是不可能的,实现注释是可能的,但是会给出编译器警告

标签: java annotations


【解决方案1】:

我从来没有在实践中使用过它,但你得到的是,你可以使用类来代替你的注释。

让我们创建一个人工示例。假设我们有一个文档生成器。它从给定的类中读取@Docu 注释并打印description 属性。像这样:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;

public class DokuGenerator {

    public static void main(String[] args) throws Exception {
        new DokuGenerator(StaticClass.class, StaticClass2.class);
    }

    public DokuGenerator(Class<?>... classesToDokument) throws Exception {
        List<Docu> documentAnnotations = getDocumentAnnotations(classesToDokument);
        printDocumentation(documentAnnotations);
    }

    private List<Docu> getDocumentAnnotations(Class<?>... classesToDokument)
            throws Exception {
        List<Docu> result = new ArrayList<Docu>();
        for (Class<?> c : classesToDokument)
            if (c.isAnnotationPresent(Docu.class))
                result.add(c.getAnnotation(Docu.class));
        return result;
    }

    private void printDocumentation(List<Docu> toDocument) {
        for (Docu m : toDocument)
            System.out.println(m.description());
    }

}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Docu {
    String description();
}

@Docu(description = "This is a static class!")
class StaticClass {
}

@Docu(description = "This is another static class!")
class StaticClass2 {
}

打印:

This is a static class!  
This is another static class!

我们现在想要完成的是,一个类不仅可以静态注释,还可以将运行时信息添加到文档中。大多数时候,我们很乐意使用@Docu 注释,但有些特殊情况我们需要特殊的文档。我们可能想为某些方法添加性能文档。我们可以通过让一个类实现注解来做到这一点。生成器首先检查注解,如果不存在,它会检查类是否实现了注解。如果是,它会将类添加到注释列表中。

像这样(生成器中只增加了两行代码):

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class DokuGenerator {

    public static void main(String[] args) throws Exception {
        new DokuGenerator(StaticClass.class, StaticClass2.class,
                DynamicClass.class);
    }

    public DokuGenerator(Class<?>... classesToDokument) throws Exception {
        List<Docu> documentAnnotations = getDocumentAnnotations(classesToDokument);
        printDocumentation(documentAnnotations);
    }

    private List<Docu> getDocumentAnnotations(Class<?>... classesToDokument)
            throws Exception {
        List<Docu> result = new ArrayList<Docu>();
        for (Class<?> c : classesToDokument)
            if (c.isAnnotationPresent(Docu.class))
                result.add(c.getAnnotation(Docu.class));
            else if (Arrays.asList(c.getInterfaces()).contains(Docu.class))
                result.add((Docu) c.newInstance());
        return result;
    }

    private void printDocumentation(List<Docu> toDocument) {
        for (Docu m : toDocument)
            System.out.println(m.description());
    }

}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Docu {
    String description();
}

@Docu(description = "This is a static class!")
class StaticClass {
}

@Docu(description = "This is another static class!")
class StaticClass2 {
}

class DynamicClass implements Docu {

    public DynamicClass() {
        try {
            Thread.sleep((long) (Math.random() * 100));
        } catch (InterruptedException e) {
            // ignore exception to make debugging a little harder
        }
    }

    @Override
    public String description() {
        long millis = System.currentTimeMillis();
        new DynamicClass();
        millis = System.currentTimeMillis() - millis;
        return "This is a dynamic class. I run on "
                + System.getProperty("os.name")
                + ". The construction of an instance of this class run for "
                + millis + " milliseconds.";
    }

    @Override
    public Class<? extends Annotation> annotationType() {
        return Docu.class;
    }

}

输出是:

This is a static class!  
This is another static class!  
This is a dynamic class. I run on Windows XP. The construction of an instance of this class run for 47 milliseconds.

您不必对代码生成器进行太多更改,因为您可以将类用作注释的替换。

其他示例可能是使用注释或 XML 作为配置的框架。您可能有一个处理注释的处理器。如果您使用 XML 作为配置,您可以生成实现注释的类实例,并且您的处理器无需任何更改即可处理它们! (当然还有其他方法可以达到相同的效果,但这是一种方法)

【讨论】:

  • 感谢您的详细回答。作为反射派生注释实例的替代品的类确实是一个有效的案例。注释作为配置数据非常方便,对于动态案例,实例化它们是必要的。
  • "// 忽略异常使调试更困难" +1
【解决方案2】:

JAXBIntroductions 是一个很好的例子:它允许使用 XML 文件配置 JAXB 注释。我想到了两个主要用例:配置您没有源代码访问权限的类或一个类的不同配置。

一般来说,我认为动态实例化注释以将它们传递给框架通常是一个很好的用例。但是,如果您是这个框架的设计者,我当然会三思而后行。

【讨论】:

    【解决方案3】:

    我在创建注释时使用它,并希望通过在省略注释时提供默认值来使其使用成为可选。当您的库引入新注释并且您希望您的库保持向后兼容时,可能会发生这种情况。

    在此示例中,BeanB 是针对旧版本库的源代码编写的,因此您希望在识别此类类时使用默认值。

    @Id
    class BeanA {}
    
    // No annotation
    class BeanB {}
    

    默认实现;

    private static final Id DEFAULT_ID = new Id() {
    
        @Override
        public IDType value() {
            return IDType.LOCAL;
        }
    
        @Override
        public Class<? extends Annotation> annotationType() {
            return Id.class;
        }
    };
    

    处理;

    Id beanId = (bean.getClass().isAnnotationPresent(Id.class))
        ? bean.getClass().getAnnotation(Id.class)
        : DEFAULT_ID;
    

    【讨论】:

    • 这当然是有效的,但你也可以提供默认值作为注释接口本身的一部分,这样你就不需要这样做了..
    • @ManoDestra 但是第二类没有注解,所以没有默认值。例如,当您希望为不使用新引入的注释的源代码提供向后兼容性支持时,可能会发生这种情况。
    【解决方案4】:

    没有有效的用户案例 - 编译器只是容忍它,因为禁止它会很麻烦,并且编写编译器的人可能在非常罕见的情况下需要该工具。如果您需要对注释进行分类,请查看这篇文章以了解如何操作:Why is not possible to extend annotations in Java?

    想象一个可怜的灵魂追随你来维护和调试该代码或另一个需要编写代码生成工具并假设注释类型是直接的或另一个只是使用此类注释的人甚至没有梦想会发生什么以及该做什么关于它。当他发现该黑客并找到消除它的方法时,他将死于疝气 - 或同等疾病:-) 注释应该是纯粹的声明性语句,仅由与注释代码分开运行的代码生成工具解释,并且将其视为数据。

    重新审视该代码并尝试诚实地说出以下内容的合理理由:

     public Class<? extends Annotation> annotationType() { 
         return Id.class; 
     } 
    

    与人们可以输入代码相比,这仍然是一件小事。

    注解不是练习黑客的地方——这就是编译器试图传达的内容。您是否确切知道注释“实现”中的代码何时以及如何运行?包括CTOR?什么是可用的,什么是不可用的?什么叫安全?编译器没有——编译器需要进行大量的静态分析才能检查这种黑客攻击的实际安全性。因此,它只是发出警告,以便当出现问题时人们不能责怪编译、VM 和其他一切。

    【讨论】:

    • 我对注释的分类不感兴趣,我的用例与编译器无关。而且我不认为我的用例是“黑客行为”。
    • @ZZX,您的示例缺少 javadoc,希望能找到 why? 的答案。
    • 你的过度反应真的没有道理。这是实现注释的一个非常有效的用例:您想要运行注释处理器,这些处理器使用笨拙的 api 提供元数据,例如 javax.lang.model,它可以避免尝试加载类值。最好编写一个注释处理器来扫描注释并生成一个实际上可以对类值进行操作的注释构建器。 (javax.lang.model 强制您捕获未声明的运行时异常并从类型镜像中提取类类型;生成的构建器负责处理其他注释处理器中的丑陋混乱)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    • 2014-07-11
    • 1970-01-01
    • 2013-04-24
    • 2018-12-01
    相关资源
    最近更新 更多