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的编译插件的配置指定如下:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<annotationProcessors>
<annotationProcessor>
test2.SourceTestAnnotationProcessor
</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
注意
-
使用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
注解使类可以使用Build模式创建
-
创建
@Builder
注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Builder {
}
-
创建实体类
Person
public 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;
}
}
关注订阅号程序猿小哈,联系作者,加入学习交流群和小伙伴们一起学习进