文章目录
自定义编译时注解处理器
使用IDEA构建的maven项目。一个生产者,一个消费者。
注解的保留类型
@Retention(RetentionPolicy.SOURCE)
.java源文件有,编译成class文件后不保存的注解,运行时也不能获取
@Retention(RetentionPolicy.CLASS)
编译后的class文件中有,但在运行时不能获取到。
@Retention(RetentionPolicy.RUNTIME)
可以在运行时获取到,即运行时注解。基于反射使用
APT简介
APT即为Annotation Processing Tool,它是javac的一个工具,中文意思为编译时注解处理器。APT可以用来在编译时扫描和处理注解。通过APT可以获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。注意,获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。APT的核心是AbstractProcessor类,关于AbstractProcessor类后面会做详细说明。
自定义注解处理器
创建一个注解
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({
ElementType.TYPE})
public @interface Data {
}
实现注解处理器
其中具体如何添加get,set方法代码原理可以看下面这篇文章。
然后继承AbstractProcessor类
@SupportedAnnotationTypes("cn.wen233.demo.processor.Data")
public class DataProcessor extends AbstractProcessor {
// 编译时期输入日志的
private Messager messager;
// 提供了待处理的抽象语法树
private JavacTrees javacTrees;
// 封装了创建AST节点的一些方法
private TreeMaker treeMaker;
// 提供了创建标识符的方法
private Names names;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
this.javacTrees = JavacTrees.instance(processingEnv);
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
this.treeMaker = TreeMaker.instance(context);
this.names = Names.instance(context);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 获取所有带有Data注解的元素,可能是类,方法,字段等
Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(Data.class);
// 对其进行遍历
elementsAnnotatedWith.forEach(e -> {
JCTree tree = javacTrees.getTree(e);
// 添加访问器
tree.accept(new TreeTranslator() {
/**
* 如果该元素是类则执行该方法
* @param jcClassDecl 类定义
*/
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
// 获取类中所有属性字段
for (JCTree jcTree : jcClassDecl.defs) {
if (jcTree.getKind().equals(Tree.Kind.VARIABLE)) {
JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) jcTree;
jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
}
}
// 对于变量进行生成方法的操作
jcVariableDeclList.forEach(jcVariableDecl -> {
messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed");
jcClassDecl.defs = jcClassDecl.defs.prepend(makeSetterMethodDecl(jcVariableDecl));
jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
});
super.visitClassDef(jcClassDecl);
}
});
});
messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + elementsAnnotatedWith.toString());
return true;
}
private JCTree.JCMethodDecl makeSetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
// 生成表达式 例如 this.a = a;
JCTree.JCExpressionStatement aThis = makeAssignment(treeMaker.Select(treeMaker.Ident(
names.fromString("this")), jcVariableDecl.getName()), treeMaker.Ident(jcVariableDecl.getName()));
statements.append(aThis);
JCTree.JCBlock block = treeMaker.Block(0, statements.toList());
// 生成形参
JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),
jcVariableDecl.getName(), jcVariableDecl.vartype, null);
List<JCTree.JCVariableDecl> parameters = List.of(param);
// 生成返回对象
JCTree.JCExpression methodType = treeMaker.Type(new Type.JCVoidType());
return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),
getNewMethodName(jcVariableDecl.getName(), "set"),
methodType,
List.nil(),
parameters,
List.nil(),
block,
null);
}
private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(
names.fromString("this")), jcVariableDecl.getName())));
JCTree.JCBlock block = treeMaker.Block(0, statements.toList());
return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),
getNewMethodName(jcVariableDecl.getName(), "get"),
jcVariableDecl.vartype,
List.nil(),
List.nil(),
List.nil(),
block,
null);
}
private Name getNewMethodName(Name name, String flag) {
String s = name.toString();
return names.fromString(flag + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
}
private JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs, JCTree.JCExpression rhs) {
return treeMaker.Exec(
treeMaker.Assign(
lhs,
rhs
)
);
}
}
添加tools.jar
因为JCTree这些类以及实现在jdk自带的tools.jar包中,所以需要将tools.jar导入到项目中。
之前看的都是导入本地tools.jar路径到项目中,但这样如果项目到别的电脑上就没有tools.jar。
所以我把tools.jar复制了一份到resources中,并将其作为maven依赖导入项目。
如图
<!-- 添加tool-->
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.5.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/tools.jar</systemPath>
</dependency>
添加SPI
在resources目录下添加META-INF目录
META-INF目录中添加services目录,
目录中新建一个javax.annotation.processing.Processor文件,
文件中放置cn.wen233.demo.processor.DataProcessor
如下图
maven打包
最后在maven打包时候又报错
错误: 服务配置文件不正确, 或构造处理程序对象javax.annotation.processing.Processor: Provider cn.wen233.demo.processor.DataProcessor not found时抛出异常错误
好像是因为项目编译的时候会使用注解处理器,此时我们自定义的注解处理器还未编译,但就已经需要使用,便报错没有找到。
最后在pom.xml中添加
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<!-- Disable annotation processing for ourselves. -->
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>
最后的pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>demo-annotation-proessor</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<!-- Disable annotation processing for ourselves. -->
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- 添加tool-->
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.5.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/tools.jar</systemPath>
</dependency>
</dependencies>
</project>
至此就可以获得一个我们自定义的注解处理器了。
使用自定义的注解处理器
新建项目
新建一个maven项目,然后将之前的注解处理器的那个jar包作为一个maven依赖导入项目。并通过配置build在项目编译时添加注解处理器。
新建项目maven的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>demo-test1</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>demo-annotation-proessor</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/demo-annotation-proessor-1.0-SNAPSHOT.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<annotationProcessors>
<annotationProcessor>
cn.wen233.demo.processor.DataProcessor
</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
</plugins>
</build>
</project>
IDEA设置
设置启用注解处理器
最后设置好了之后的情况。
遇到的问题
1、使用自定义注解处理器时报错Error:java: Compilation failed: internal java compiler error
有可能是因为编译时需要使用注解处理器,但自定义的注解处理器尚未加载。可以配置IDEAAnnotation Processing;也可以不先去掉注解,然后构建一次项目。
2、未找到ExceptionCode,需要导入
import com.wen233.processor.ExceptionCode;
3、报错信息 java: -source 1.5 中不支持 diamond 运算符
在maven pom.xml中添加
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
……