Java 6注释处理 - 从注释获取类

60

我有一个称为@Pojo的自定义注解,用于自动生成wiki文档:

package com.example.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface Pojo {
    Class<?> value();
}
我这样使用它:
@Pojo(com.example.restserver.model.appointment.Appointment.class)

为了使注解处理器能够自动生成描述资源和期望类型的维基页面,需要对资源方法进行注释。

我需要在注解处理器中读取注解中的value字段的值,但是我遇到了运行时错误。

在我的处理器源代码中,我有以下几行:

final Pojo pojo = element.getAnnotation(Pojo.class);
// ...
final Class<?> pojoJavaClass = pojo.value();

但实际的类对处理器不可用。我认为我需要一个javax.lang.model.type.TypeMirror作为真实类的代理。我不确定如何获得一个。

我得到的错误信息是:

javax.lang.model.type.MirroredTypeException: Attempt to access Class object for TypeMirror com.example.restserver.model.appointment.Appointment

Appointment 是我一个 @Pojo 注解中提到的类。

不幸的是,关于 Java 注解处理的文档和/或教程似乎很少。已经尝试了谷歌搜索。


2
很少有人会问这种问题,但对于我们这些生活在(Java)塔耳塔洛斯的人来说,得到他人的支持非常有用。 - Ordiel
4个回答

65
我是来问完全相同的问题的。...并发现Ralph发布了相同的博客链接
这是一篇很长但非常丰富的文章。故事摘要--有两种方法可以做到,一种是简单的方式,另一种是“更正确”的方式。
这是简单的方法:
private static TypeMirror getMyValue1(MyAnnotation annotation) {
    try
    {
        annotation.myValue(); // this should throw
    }
    catch( MirroredTypeException mte )
    {
        return mte.getTypeMirror();
    }
    return null; // can this ever happen ??
}

另一种更繁琐的方法(没有例外):
private static AnnotationMirror getAnnotationMirror(TypeElement typeElement, Class<?> clazz) {
    String clazzName = clazz.getName();
    for(AnnotationMirror m : typeElement.getAnnotationMirrors()) {
        if(m.getAnnotationType().toString().equals(clazzName)) {
            return m;
        }
    }
    return null;
}

private static AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String key) {
    for(Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet() ) {
        if(entry.getKey().getSimpleName().toString().equals(key)) {
            return entry.getValue();
        }
    }
    return null;
}


public TypeMirror getMyValue2(TypeElement foo) {
    AnnotationMirror am = getAnnotationMirror(foo, MyAnnotation.class);
    if(am == null) {
        return null;
    }
    AnnotationValue av = getAnnotationValue(am, "myValue");
    if(av == null) {
        return null;
    } else {
        return (TypeMirror)av.getValue();
    }
}

当然,一旦你获得了TypeMirror,你(至少在我的经验中)几乎总是希望获得一个TypeElement:

private TypeElement asTypeElement(TypeMirror typeMirror) {
    Types TypeUtils = this.processingEnv.getTypeUtils();
    return (TypeElement)TypeUtils.asElement(typeMirror);
}

......在第一次理清楚之前,最后那个微不足道的小问题让我抓耳挠腮了一个小时。这些注解处理器实际上并不难编写,只是API一开始非常混乱且冗长,让人感到烦恼。我很想发布一个帮助类,使所有基本操作变得明显...但这是另外一天的故事(如果你需要,请私信我)。


我也卡在这里了。如果您使用了什么辅助工具,能否请发布一个gist? - gorodechnyj
1
@gorodechnyj - 这是我当时编写的代码:https://github.com/ddopson/EpicFramework/blob/uiv2/EpicBuilder/src/com/epic/framework/build/EpicAnnotationProcessor.java 请随意查看并自由借鉴。 - Dave Dopson
2
我认为+1对于这个答案来说不够,应该是+10。 - Mohamed El-Beltagy
1
@MohamedEl-Beltagy - 非常感谢您的赞美之词! - Dave Dopson
这是很棒的东西。我只能赞同@MohamedEl-Beltagy。这里有另一篇关于同一主题的精彩文章,供有兴趣的人阅读。http://hauchee.blogspot.com/2015/12/compile-time-annotation-processing-getting-class-value.html - Lukasz
@DaveDopson 类 com.sun.tools.javac.code.Attribute$Class 无法转换为类 javax.lang.model.type.TypeMirror - kylie.zoltan

33

1
这可能会奏效,尽管“胶带和口香糖”浮现在脑海中。顺便说一句,用户名很酷 :-). - Ralph

2

我的答案基本上和Dave Dopson的一样,只是代码稍微改了一下:

public default Optional<? extends AnnotationMirror> getAnnotationMirror( final Element element, final Class<? extends Annotation> annotationClass )
{
    final var annotationClassName = annotationClass.getName();
    final Optional<? extends AnnotationMirror> retValue = element.getAnnotationMirrors().stream()
        .filter( m -> m.getAnnotationType().toString().equals( annotationClassName ) )
        .findFirst();

    return retValue;
}

他的代码将TypeElement作为getAnnotationMirror()方法的第一个参数类型,仅限于类的使用。将其更改为Element可用于任何带注释元素。 基于流的实现而不是原始的for循环只是一种口味问题。

public default Optional<? extends AnnotationValue> getAnnotationValue( final AnnotationMirror annotationMirror, final String name )
{
    final Elements elementUtils = this.processingEnv.getElementUtils();
    final Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = elementUtils.getElementValuesWithDefaults( annotationMirror );
    final Optional<? extends AnnotationValue> retValue = elementValues.keySet().stream()
        .filter( k -> k.getSimpleName().toString().equals( name ) )
        .map( k -> elementValues.get( k ) )
        .findAny();

    return retValue;
}

Dave使用Element.getElementValues()来获取注解值,但这只返回那些明确应用于具体注解的值。默认值将被省略。 方法Elements.getElementValuesWithDefaults()将返回默认值(从它的名称中你可能已经猜到了)。


1
这是我的技巧:
public class APUtils {

  @FunctionalInterface
  public interface GetClassValue {
    void execute() throws MirroredTypeException, MirroredTypesException;
  }

  public static List<? extends TypeMirror> getTypeMirrorFromAnnotationValue(GetClassValue c) {
    try {
      c.execute();
    }
    catch(MirroredTypesException ex) {
      return ex.getTypeMirrors();
    }
    return null;
  }

}

然后我可以使用任何注释值:
public @interface MyAnnotationType {
  public Class<?>[] value() default {};
}

例子

@MyAnnotationType(String.class)
public class Test { ... }

在注解处理器中的用法

MyAnnotationType a = element.getAnnotation(MyAnnotationType.class);
List<? extends TypeMirror> types = APUtils.getTypeMirrorFromAnnotationValue(() -> a.value());

测试使用的是JDK 1.8.0_192


Class<?>[] value() default {}; 这正是我要找的! - lvl4fi4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接