实现一个AnnotationProcessor
一、什么是Annotation Processor
注解大家肯定都很常用,Annotation Processor就是专门在编译期处理注解用的,像lombok就是这样的。编译期做的好处就是不用像在运行期处理注解那样,到处调反射api,性能更高。
二、实现
实现一个@ToString的注解,能够标注在类上,在编译期间重写一个toString方法,让toString方法返回对象的json字符串
https://github.com/Hayaking/java-mate
2.1 声明注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface ToString {
ToStringEnum type() default ToStringEnum.JSON;
}
public enum ToStringEnum {
JSON
}
2.2 声明Annotation Processor
import com.haya.mate.core.annotation.ToString;
import com.haya.mate.core.template.ToStringTemplateGenerator;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Names;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import java.util.Set;
@SupportedSourceVersion(SourceVersion.RELEASE_11)
@SupportedAnnotationTypes("com.haya.mate.core.annotation.ToString")
public class ToStringProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
processWrap(annotations, roundEnv);
} catch (Exception e) {
return false;
}
return false;
}
public void processWrap(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
var context = ((JavacProcessingEnvironment) processingEnv).getContext();
var trees = JavacTrees.instance(processingEnv);
var elementUtils = (JavacElements) processingEnv.getElementUtils();
var treeMaker = TreeMaker.instance(context);
var names = Names.instance(context);
roundEnv.getElementsAnnotatedWith(ToString.class)
.stream()
.map(item -> {
TreePath path = trees.getPath(item);
ToString annotation = item.getAnnotation(ToString.class);
return List.of(annotation, elementUtils.getTree(item), path);
})
.forEach(list -> {
ToString annotation = (ToString) list.get(0);
JCTree classDef = (JCTree) list.get(1);
TreePath path = (TreePath) list.get(2);
classDef.accept(new JCTree.Visitor() {
@Override
public void visitClassDef(JCTree.JCClassDecl classDecl) {
// 在这里生成新的方法
var methodTemplate = ToStringTemplateGenerator.getMethodTemplate(
path, elementUtils, annotation, treeMaker, names
);
// 追加到class文件里
classDecl.defs = classDecl.defs.append(methodTemplate);
}
});
});
}
}
2.3 生成方法
package com.haya.mate.core.template;
import com.haya.mate.core.annotation.ToString;
import com.haya.mate.core.annotation.ToStringEnum;
import com.haya.mate.core.service.JavaMateJsonService;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
import java.util.Objects;
public class ToStringTemplateGenerator {
public static JCTree.JCMethodDecl getMethodTemplate(
TreePath trees,
JavacElements elementUtils,
ToString annotationInfo,
TreeMaker treeMaker,
Names names
) {
var compilationUnit = (JCTree.JCCompilationUnit) trees.getCompilationUnit();
// import类的全路径
var imports = new ListBuffer<JCTree>();
imports.append(compilationUnit.defs.get(0));
// 把JavaMateJsonService import进来
{
var name = JavaMateJsonService.class.getPackage().getName();
var simpleName = JavaMateJsonService.class.getSimpleName();
var packageIdent = treeMaker.Ident(names.fromString(name));
var fieldAccess = treeMaker.Select(packageIdent,
names.fromString(simpleName));
imports.append(treeMaker.Import(fieldAccess, false));
}
// 把不变的追加进来
compilationUnit.defs.forEach(imports::append);
// 覆盖之前的import
compilationUnit.defs = imports.toList();
// 开始重写toString方法
if (Objects.equals(annotationInfo.type(), ToStringEnum.JSON)) {
return getToJsonMethodTemplate(elementUtils, treeMaker, names);
}
throw new RuntimeException("!!!!!!!type no value!!!!!!!!");
}
public static JCTree.JCMethodDecl getToJsonMethodTemplate(JavacElements elementUtils, TreeMaker treeMaker, Names names) {
var modifiers = treeMaker.Modifiers(Flags.PUBLIC);
var name = names.fromString("toString");
// 生成return语句
/**
* 生成出来的长这样:return JavaMateJsonService.toJson(this);
*/
var returnStatement = treeMaker.Return(
treeMaker.Apply(
List.nil(),
treeMaker.Select(
treeMaker.Ident(
elementUtils.getName("JavaMateJsonService")
),
elementUtils.getName("toJson")
),
List.of(treeMaker.Ident(names.fromString("this")))
)
);
var body = treeMaker.Block(0, List.of(returnStatement));
var returnType = treeMaker.Ident(names.fromString("String"));
// 泛型参数列表
List<JCTree.JCTypeParameter> methodGenericParamList = List.nil();
// 参数值列表
List<JCTree.JCVariableDecl> parameterList = List.nil();
// 异常抛出列表
List<JCTree.JCExpression> throwCauseList = List.nil();
return treeMaker.MethodDef(modifiers, name, returnType,
// 泛型参数列表
methodGenericParamList,
//参数值列表
parameterList,
// 异常抛出列表
throwCauseList,
// 方法默认体
body,
null
);
}
}
2.4 利用SPI声明Json处理器
具体可参考讲SPI的那篇文章
2.5 测试效果
声明一个java bean
@Getter
@Setter
// 自己声明的@ToString方法
@ToString
public class Test {
private int field=0;
private int field2=0;
}
public class Main {
public static void main(String[] args) {
System.out.print(new Test());
}
}
{"field":0,"field2":0}
三、注意
如果只是实现着玩玩,推荐用java8。java9之后因为module模块化的引入,出现了大坑。
如果java9及以后要使用的话
module-info参考以下配置:
module-info.java
module java.mate {
uses com.haya.mate.core.spi.JavaMateJsonHandler;
requires jdk.compiler;
}
maven参考以下配置:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${pVersion.compiler}</version>
<configuration>
<source>11</source>
<target>11</target>
<verbose>true</verbose>
<compilerArgs>
<arg>--add-exports</arg><arg>jdk.compiler/com.sun.tools=java.mate</arg>
<arg>--add-exports</arg><arg>jdk.compiler/com.sun.tools.javac.model=java.mate</arg>
<arg>--add-exports</arg><arg>jdk.compiler/com.sun.tools.javac.processing=java.mate</arg>
<arg>--add-exports</arg><arg>jdk.compiler/com.sun.tools.javac.code=java.mate</arg>
<arg>--add-exports</arg><arg>jdk.compiler/com.sun.tools.javac.tree=java.mate</arg>
<arg>--add-exports</arg><arg>jdk.compiler/com.sun.tools.javac.util=java.mate</arg>
<arg>--add-exports</arg><arg>jdk.compiler/com.sun.tools.javac.api=java.mate</arg>
</compilerArgs>
</configuration>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
<includes>
<!-- 自定义的注解处理器全限定名-->
<include>com.haya.mate.core.annotation.ToString</include>
</includes>
</configuration>
</execution>
<execution>
<id>compile-project</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Loading...