使用IDEA构建的maven项目。一个生产者,一个消费者。
github地址
注解的保留类型
@Retention(RetentionPolicy.SOURCE) .java源文件有,编译成class文件后不保存的注解,运行时也不能获取
@Retention(RetentionPolicy.CLASS) 编译后的class文件中有,但在运行时不能获取到。
@Retention(RetentionPolicy.RUNTIME) 可以在运行时获取到,即运行时注解。基于反射使用
APT即为Annotation Processing Tool,它是javac的一个工具,中文意思为编译时注解处理器。APT可以用来在编译时扫描和处理注解。通过APT可以获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。注意,获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。APT的核心是AbstractProcessor类,关于AbstractProcessor类后面会做详细说明。
其中具体如何添加get,set方法代码原理可以看下面这篇文章。
Java中的屠龙之术——如何修改语法树
然后继承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 ) ); } }因为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>在resources目录下添加META-INF目录 META-INF目录中添加services目录, 目录中新建一个javax.annotation.processing.Processor文件, 文件中放置cn.wen233.demo.processor.DataProcessor 如下图
最后在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>设置启用注解处理器 最后设置好了之后的情况。
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> ……