java基础之编译器注解
上一篇我们讲到了如何实现编译器的注解,本期让我们来看一下源文件注解的创建和使用.
在JAVA1.6后,以后不再默认包含APT了,并且相关的资源都被移除了比如Mirror相关类.提供了新的插件化注解处理API(Pluggable Annotation Processing API).
毕竟现在大家都是1.8或以上版本(嗯 是时候搞个jdk11了),本文就介绍新的插件化注解处理API(原理差不多),若对原有的APT处理有兴趣的小伙伴可以参照java编程思想注解部分学习(这么经典的书,不可能没有是不是,真没有?小哈提供了电子版下载地址关注公众号私聊小哈获取)
什么是插件化注解处理API
插件化注解处理(Pluggable Annotation Processing)APIJSR 269提供一套标准API来处理AnnotationsJSR 175.实际上JSR 269不仅仅用来处理Annotation,我觉得更强大的功能是它建立了Java 语言本身的一个模型,它把method、package、constructor、type、variable、enum、annotation等Java语言元素映射为Types和Elements,从而将Java语言的语义映射成为对象,我们可以在javax.lang.model包下面可以看到这些类。所以我们可以利用JSR 269提供的API来构建一个功能丰富的元编程(metaprogramming)环境。JSR 269用Annotation Processor在编译期间而不是运行期间处理Annotation(源文件注解), Annotation Processor相当于编译器的一个插件,所以称为插入式注解处理.如果Annotation Processor处理Annotation时(执行process()方法)产生了新的Java代码,编译器会再调用一次Annotation Processor,如果第二次处理还有新代码产生,就会接着调用Annotation Processor,直到没有新代码产生为止(可以理解为递归调用到无可调用)。每执行一次process()方法被称为一个round,这样整个Annotation processing过程可以看作是一个round的序列。JSR 269主要被设计成为针对Tools或者容器的API。这个特性虽然在JavaSE 6已经存在,但是很少人知道它的存在.(lombok就是使用这个特性实现编译期的代码插入的)
Pluggable Annotation Processing API的核心是Annotation Processor即注解处理器,一般需要继承抽象类javax.annotation.processing.AbstractProcessor。注意,与运行时注解RetentionPolicy.RUNTIME不同,注解处理器只会处理编译期注解,也就是RetentionPolicy.SOURCE的注解类型,处理的阶段位于Java代码编译期间
使用步骤
-
自定义源注解:
@Retention(RetentionPolicy.SOURCE) -
自定义任务处理器(Annotation Processor):
继承javax.annotation.processing.AbstractProcessor,并覆写process方法. Annotation Processor中使用javax.annotation.processing.SupportedAnnotationTypes指定注解类型的名称(注意需要全类名,"包名.注解类型名称",否则会不生效). Annotation Processor中使用javax.annotation.processing.SupportedSourceVersion指定编译版本. Annotation Processor中使用javax.annotation.processing.SupportedOptions 指定编译参数 可选参数
-
使用:在类上添加自定义注解
demo1 基础版
-
创建注解
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) public @interface SourceTest { public String value() default "默认值"; }
-
定义一个注解处理器
@SupportedAnnotationTypes(value = {"test2.SourceTest"}) @SupportedSourceVersion(value = SourceVersion.RELEASE_8) public class SourceTestAnnotationProcessor extends AbstractProcessor {
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { System.out.println("Log in AnnotationProcessor.process"); for (TypeElement typeElement : annotations) { System.out.println(typeElement); } System.out.println(roundEnv); return true; }}
-
编写测试主类
public class SourceTestMain { public static void main(String[] args) throws Exception { System.out.println("开始插入式注解测试"); test(); } @SourceTest(value = "测试编译器注解") public static void test()throws Exception{ } }
编译方式
-
直接使用编译参数指定,例如:javac -processor test2.SourceTestAnnotationProcessor SourceTestMain.java。
-
通过服务注册指定,就是META-INF/services/javax.annotation.processing.Processor文件中添加test2.SourceTestAnnotationProcessor.
-
通过Maven的编译插件的配置指定如下:
org.apache.maven.plugins maven-compiler-plugin 3.5.1 1.8 1.8 UTF-8 test2.SourceTestAnnotationProcessor
注意
-
使用idea时需要开启注解支持,如图所示
-
使用前注解处理器SourceTestAnnotationProcessor必须先被编译,不然会得到下面的报错信息
运行成功得到结果
Log in AnnotationProcessor.process
[errorRaised=false, rootElements=[club.throwable.processor.Test,club.throwable.processor.Main, club.throwable.processor.AnnotationProcessor, processingOver=false]
Log in AnnotationProcessor.process
[errorRaised=false, rootElements=[], processingOver=false]
Log in AnnotationProcessor.process
[errorRaised=false, rootElements=[], processingOver=true]
进阶版 类lombok注解
设置一个[@Builder](https://my.oschina.net/u/1245189)注解使类可以使用Build模式创建
-
创建
[@Builder](https://my.oschina.net/u/1245189)注解@Target({ElementType.METHOD}) @Retention(RetentionPolicy.SOURCE) public @interface Builder { }
-
创建实体类
Personpublic class Person {
private Integer age; private String name; public Integer getAge() { return age; } @Builder public void setAge(Integer age) { this.age = age; } public String getName() { return name; } @Builder public void setName(String name) { this.name = name; }}
-
创建注解处理器
BuilderProcessor@SupportedAnnotationTypes(value = {"club.throwable.processor.builder.Builder"}) @SupportedSourceVersion(value = SourceVersion.RELEASE_8) public class BuilderProcessor extends AbstractProcessor {
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (TypeElement typeElement : annotations) { Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(typeElement); Map<Boolean, List<Element>> annotatedMethods = annotatedElements.stream().collect(Collectors.partitioningBy( element -> ((ExecutableType) element.asType()).getParameterTypes().size() == 1 && element.getSimpleName().toString().startsWith("set"))); List<Element> setters = annotatedMethods.get(true); List<Element> otherMethods = annotatedMethods.get(false); otherMethods.forEach(element -> processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@Builder must be applied to a setXxx method " + "with a single argument", element)); Map<String, String> setterMap = setters.stream().collect(Collectors.toMap( setter -> setter.getSimpleName().toString(), setter -> ((ExecutableType) setter.asType()) .getParameterTypes().get(0).toString() )); String className = ((TypeElement) setters.get(0) .getEnclosingElement()).getQualifiedName().toString(); try { writeBuilderFile(className, setterMap); } catch (IOException e) { e.printStackTrace(); } } return true; } private void writeBuilderFile( String className, Map<String, String> setterMap) throws IOException { String packageName = null; int lastDot = className.lastIndexOf('.'); if (lastDot > 0) { packageName = className.substring(0, lastDot); } String simpleClassName = className.substring(lastDot + 1); String builderClassName = className + "Builder"; String builderSimpleClassName = builderClassName .substring(lastDot + 1); JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(builderClassName); try (PrintWriter out = new PrintWriter(builderFile.openWriter())) { if (packageName != null) { out.print("package "); out.print(packageName); out.println(";"); out.println(); } out.print("public class "); out.print(builderSimpleClassName); out.println(" {"); out.println(); out.print(" private "); out.print(simpleClassName); out.print(" object = new "); out.print(simpleClassName); out.println("();"); out.println(); out.print(" public "); out.print(simpleClassName); out.println(" build() {"); out.println(" return object;"); out.println(" }"); out.println(); setterMap.forEach((methodName, argumentType) -> { out.print(" public "); out.print(builderSimpleClassName); out.print(" "); out.print(methodName); out.print("("); out.print(argumentType); out.println(" value) {"); out.print(" object."); out.print(methodName); out.println("(value);"); out.println(" return this;"); out.println(" }"); out.println(); }); out.println("}"); } }}
-
提起编译注解处理器
javac BuilderProcessor.java
-
编译
META-INF/services/javax.annotation.processing.Processor文件中添加test3.BuilderProcessor
执行mvn compile
-
结果生成
PersonBuilder类public class PersonBuilder { private Person object = new Person();
public PersonBuilder() { } public Person build() { return this.object; } public PersonBuilder setName(String value) { this.object.setName(value); return this; } public PersonBuilder setAge(Integer value) { this.object.setAge(value); return this; }}
关注订阅号程序猿小哈,联系作者,加入学习交流群和小伙伴们一起学习进步