【问题标题】:Java Annotations values provided in dynamic manner以动态方式提供的 Java 注释值
【发布时间】:2012-05-25 01:23:53
【问题描述】:

我想为一些方法生成的一些值提供注解。

到目前为止我已经试过了:

public @interface MyInterface {
    String aString();
}

@MyInterface(aString = MyClass.GENERIC_GENERATED_NAME)
public class MyClass {

    static final String GENERIC_GENERATED_NAME = MyClass.generateName(MyClass.class);

    public static final String generateName(final Class<?> c) {
        return c.getClass().getName();
    }
}

以为GENERIC_GENERATED_NAMEstatic final,它抱怨说

注解属性MyInterface.aString的值必须是常量表达式

那么如何实现呢?

【问题讨论】:

    标签: java annotations


    【解决方案1】:

    没有办法像其他人所说的那样动态修改注释的属性。不过,如果您想实现这一目标,有两种方法可以做到这一点。

    1. 将表达式分配给注释中的属性,并在检索注释时处理该表达式。在您的情况下,您的注释可以是

      @MyInterface(aString = "objectA.doSomething(args1, args2)")

    当您阅读时,您可以处理字符串并进行方法调用并检索值。 Spring 通过 SPEL(Spring 表达式语言)来做到这一点。这是资源密集型的,每次我们想要处理表达式时都会浪费 cpu 周期。如果您使用的是 spring,您可以挂接一个 beanPostProcessor 并处理一次表达式并将结果存储在某处。 (可以是全局属性对象,也可以是可以在任何地方检索的地图)。

    1. 这是做我们想做的事的一种骇人听闻的方式。 Java 存储了一个私有变量,该变量维护类/字段/方法上的注释映射。您可以使用反射并获取该地图。因此,在第一次处理注释时,我们解析表达式并找到实际值。然后我们创建所需类型的注释对象。我们可以将新创建​​的带有实际值(常量)的注解放在注解的属性上,并覆盖检索到的地图中的实际注解。

    jdk 存储注解映射的方式取决于 java 版本,并且不可靠,因为它没有公开使用(它是私有的)。

    您可以在此处找到参考实现。

    https://rationaleemotions.wordpress.com/2016/05/27/changing-annotation-values-at-runtime/

    P.S:第二种方法我还没试过。

    【讨论】:

      【解决方案2】:

      解决方案是改用带注释的方法。调用该方法(使用反射)以获取动态值。

      从用户的角度来看,我们应该:

      @MyInterface
      public class MyClass {
          @MyName
          public String generateName() {
              return MyClass.class.getName();
          }
      }
      

      注释本身将被定义为

      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.METHOD)
      public @interface @MyName {
      }
      

      实现这两个注释的查找相当简单。

      // as looked up by @MyInterface
      Class<?> clazz;
      
      Method[] methods = clazz.getDeclaredMethods();
      if (methods.length != 1) {
          // error
      }
      Method method = methods[0];
      if (!method.isAnnotationPresent(MyName.class)) {
          // error as well
      }
      // This works if the class has a public empty constructor
      // (otherwise, get constructor & use setAccessible(true))
      Object instance = clazz.newInstance();
      // the dynamic value is here:
      String name = (String) method.invoke(instance);
      

      【讨论】:

      【解决方案3】:

      无法动态生成注释中使用的字符串。编译器在编译时评估 RetentionPolicy.RUNTIME 注释的注释元数据,但 GENERIC_GENERATED_NAME 直到运行时才知道。并且您不能将生成的值用于 RetentionPolicy.SOURCE 的注释,因为它们在编译后被丢弃,因此这些生成的值将永远不为人知。

      【讨论】:

      • 好的,所以我知道这不能通过使用注释来解决,对吧?也许我将不得不在编译之前运行一些工具并完成 Java 源代码。还有其他建议吗?
      • @thelost 老实说,我从来没有仔细研究过。今天有空让我看看周围,我会尽力回复你。
      • 很抱歉在 6 年后回复此线程:D,但有没有办法在注释中提供动态值,例如,如果我们希望用户指定一个文件名,并在注释(例如 Spring AOP 中的切入点)?
      • @DhruvSinghal 如果你在编译时有文件,你可以使用这种方法:stackoverflow.com/questions/6525059/…。否则,您无法在运行时修改源文件,因此您也无法修改注解值。
      猜你喜欢
      • 2013-10-02
      • 1970-01-01
      • 1970-01-01
      • 2017-04-02
      • 2010-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多