Java注解大家都了解,平时我们使用最多的就是在运行时也有效的注解:@Retention(RetentionPolicy.RUNTIME)
,可以根据这类注解在运行时进行一些特殊的逻辑处理,如Spring中的AOP使用。但是除了这类在运行时存在的注解,还有两种会保留到源码@Retention(RetentionPolicy.SOURCE)
和字节码@Retention(RetentionPolicy.CLASS)
中的注解,这种注解有什么作用呢?
这里介绍一种比较常见的用法-Java注解处理器(Java Annotation Process),它可以在运行时获取注解信息,生成一些额外的文件信息,如我们常用的lombok
或mapstruct
都是使用这种技术
下面我们通过一个非常简单的例子来介绍一个它的用法:给前端提供接口时,后端在编写接口文档时,经常会需要将一个类的结构转成json结构并添加注解后提供给前端,这里我们通过注解,在编译时生成类的结构信息
定义注解
首先创建一个定义注解的模板,我们定义如下两个注解
1 2 3 4 5 6
| @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) public @interface ClassDoc { String desc() default ""; }
|
1 2 3 4 5 6
| @Target(ElementType.FIELD) @Retention(RetentionPolicy.CLASS) public @interface FieldDoc { String desc() default ""; }
|
创建注解处理器
创建用于根据注解生成文档的注解处理器,因为Processor需要SPI方式声明,即在META-INFO/services路径下创建文件(javax.annotation.processing.Processor)并在其中声明对应的实现com.github.zavier.processor.DocProcessor
这里我们使用google的 auto-service来简化声明流程
1 2 3 4 5
| <dependency> <groupId>com.google.auto.service</groupId> <artifactId>auto-service</artifactId> <version>1.0</version> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
|
@SupportedAnnotationTypes({"com.github.zavier.annotation.ClassDoc"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class) public class DocProcessor extends AbstractProcessor {
private final ConcurrentHashMap<String, List<FieldDocInfo>> typeDocMap = new ConcurrentHashMap<>();
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { try { if (roundEnv.processingOver()) { StringBuilder sb = new StringBuilder(); typeDocMap.forEach((typeName, docInfoList) -> { sb.append("# ").append(typeName).append("\r\n").append("{\r\n"); for (FieldDocInfo fieldDocInfo : docInfoList) { sb.append(" ").append("# ").append(fieldDocInfo.desc).append("\r\n"); sb.append(" ").append(fieldDocInfo.name).append(": ").append(fieldDocInfo.type).append("\r\n"); } sb.append("}\r\n"); });
final FileObject resource = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", "doc/doc.txt"); try (OutputStream out = resource.openOutputStream()) { BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, UTF_8)); writer.write(sb.toString()); writer.newLine(); writer.flush(); } } else { final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(ClassDoc.class) .stream() .filter(TypeElement.class::isInstance) .collect(Collectors.toSet()); for (Element element : elements) { final String typeElementName = element.getAnnotation(ClassDoc.class).desc(); final List<FieldDocInfo> collect = element.getEnclosedElements() .stream() .filter(VariableElement.class::isInstance) .map(VariableElement.class::cast) .map(ele -> { final String name = ele.getSimpleName().toString(); final String desc = ele.getAnnotation(FieldDoc.class).desc(); final TypeKind kind = ele.asType().getKind(); if (kind == TypeKind.DECLARED) { final TypeMirror typeMirror = ele.asType(); final DeclaredType mirror = (DeclaredType) typeMirror; TypeElement e = (TypeElement) (mirror).asElement(); final Name simpleName = e.getSimpleName(); return new FieldDocInfo(name, desc, simpleName.toString()); } else { return new FieldDocInfo(name, desc, kind.name()); } }).collect(Collectors.toList()); typeDocMap.put(typeElementName, collect); } } } catch (IOException e) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage()); } return false; }
static class FieldDocInfo { private String name; private String desc; private String type;
public FieldDocInfo() { }
public FieldDocInfo(String name, String desc, String type) { this.name = name; this.desc = desc; this.type = type; }
} }
|
下面来看下使用方式
1 2 3 4 5 6 7 8 9
| @ClassDoc(desc = "用户") public class User { @FieldDoc(desc = "姓名") private String name;
@FieldDoc(desc = "年龄") private Integer age;
}
|
引入处理器的包,编辑后即可在maven target目录下找到生成的文档文件
项目Demo地址:https://github.com/zavier/annotation-processor
参考资料
https://www.baeldung.com/java-annotation-processing-builder
https://www.jianshu.com/p/5ca05317286e