java注解-使用注解处理器实现动态生成get和set方法
一、简介
本文将介绍如何创建一个注解处理器实现 lombok 插件中的 @Data 功能,用过 @Data 注解的小伙伴都知道他会自动帮你创建所有字段的 get 和 set 方法。
项目地址:https://github.com/1277463718lmt/apt-demo.git
二、如何实现
1. 环境说明:
java1.8、idea、maven
2. 创建项目
项目中有两个模块,apt-test 主要使用来测试的,apt-tool 是实现注解处理器的。
apt-tool 模块 pom.xml 需要引入的依赖
<dependencies>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc2</version>
</dependency>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>
apt-test 模块 pom.xml 需要引入的依赖
<!--apt-tool 模块 -->
<dependencies>
<dependency>
<artifactId>apt-tool</artifactId>
<groupId>com.linmt</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<span class="hljs-tag"><<span class="hljs-name">build</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugins</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.apache.maven.plugins<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>maven-compiler-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>3.10.1<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugins</span>></span>
<span class="hljs-tag"></<span class="hljs-name">build</span>></span>
3. 定义 @Data 注解
@Retention 只需要定义成 RetentionPolicy.SOURCE,运行时不需要使用该注解。
package com.linmt.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
}
4. 定义 @Data 的注解处理器
创建一个注解处理器类 DataProcessor,继承 AbstractProcessor 类,记得在类上加上 @AutoService(Processor.class) 注解。
通过重写 process 方法实现动态修改 class 文件的内容。
其中需要用到 java 抽象语法树较为复杂,api 可参考网上资料。
https://blog.csdn.net/youanyyou/article/details/120582478
package com.linmt.processor;
import com.google.auto.service.AutoService;
import com.linmt.annotation.Data;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
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.tree.TreeTranslator;
import com.sun.tools.javac.util.*;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.LinkedHashSet;
import java.util.Set;
// 该注解省去了手动在 /resources/META-INF/services/ 下创建 javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
public class DataProcessor extends AbstractProcessor {
private Messager messager;
private JavacTrees javacTrees;
private TreeMaker treeMaker;
private Names names;
<span class="hljs-comment">// 定义需要处理的注解</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-title class_">Set</span><<span class="hljs-title class_">String</span>> <span class="hljs-title function_">getSupportedAnnotationTypes</span>(<span class="hljs-params"></span>) {
<span class="hljs-title class_">Set</span><<span class="hljs-title class_">String</span>> annotations = <span class="hljs-keyword">new</span> <span class="hljs-title class_">LinkedHashSet</span><>();
annotations.<span class="hljs-title function_">add</span>(<span class="hljs-title class_">Data</span>.<span class="hljs-property">class</span>.<span class="hljs-title function_">getCanonicalName</span>());
<span class="hljs-keyword">return</span> annotations;
}
<span class="hljs-comment">// 版本支持</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-title class_">SourceVersion</span> <span class="hljs-title function_">getSupportedSourceVersion</span>(<span class="hljs-params"></span>) {
<span class="hljs-keyword">return</span> <span class="hljs-title class_">SourceVersion</span>.<span class="hljs-title function_">latestSupported</span>();
}
<span class="hljs-comment">// 初始化</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> synchronized <span class="hljs-built_in">void</span> <span class="hljs-title function_">init</span>(<span class="hljs-params">ProcessingEnvironment processingEnv</span>) {
<span class="hljs-variable language_">super</span>.<span class="hljs-title function_">init</span>(processingEnv);
<span class="hljs-variable language_">this</span>.<span class="hljs-property">messager</span> = processingEnv.<span class="hljs-title function_">getMessager</span>();
<span class="hljs-variable language_">this</span>.<span class="hljs-property">javacTrees</span> = <span class="hljs-title class_">JavacTrees</span>.<span class="hljs-title function_">instance</span>(processingEnv);
<span class="hljs-title class_">Context</span> context = ((<span class="hljs-title class_">JavacProcessingEnvironment</span>) processingEnv).<span class="hljs-title function_">getContext</span>();
<span class="hljs-variable language_">this</span>.<span class="hljs-property">treeMaker</span> = <span class="hljs-title class_">TreeMaker</span>.<span class="hljs-title function_">instance</span>(context);
<span class="hljs-variable language_">this</span>.<span class="hljs-property">names</span> = <span class="hljs-title class_">Names</span>.<span class="hljs-title function_">instance</span>(context);
<span class="hljs-title function_">log</span>(<span class="hljs-string">"初始化...."</span>);
}
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-built_in">boolean</span> <span class="hljs-title function_">process</span>(<span class="hljs-params"><span class="hljs-built_in">Set</span><? <span class="hljs-keyword">extends</span> TypeElement> annotations, RoundEnvironment roundEnv</span>) {
<span class="hljs-title class_">Set</span><? <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Element</span>> elements = roundEnv.<span class="hljs-title function_">getElementsAnnotatedWith</span>(<span class="hljs-title class_">Data</span>.<span class="hljs-property">class</span>);
<span class="hljs-keyword">for</span> (<span class="hljs-title class_">Element</span> element : elements) {
<span class="hljs-title function_">log</span>(<span class="hljs-string">"当前类="</span> + element.<span class="hljs-title function_">getSimpleName</span>());
<span class="hljs-title class_">JCTree</span> jcTree = javacTrees.<span class="hljs-title function_">getTree</span>(element);
<span class="hljs-comment">// 以下这段代码解决报错java.lang.AssertionError: Value of x -1</span>
treeMaker.<span class="hljs-property">pos</span> = jcTree.<span class="hljs-property">pos</span>;
jcTree.<span class="hljs-title function_">accept</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">TreeTranslator</span>() {
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-built_in">void</span> <span class="hljs-title function_">visitClassDef</span>(<span class="hljs-params">JCTree.JCClassDecl jcClassDecl</span>) {
<span class="hljs-title class_">List</span><<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCVariableDecl</span>> jcVariableDeclList = <span class="hljs-title class_">List</span>.<span class="hljs-title function_">nil</span>();
<span class="hljs-comment">// jcClassDecl.defs为类里面的所有定义</span>
<span class="hljs-keyword">for</span> (<span class="hljs-title class_">JCTree</span> tree : jcClassDecl.<span class="hljs-property">defs</span>) {
<span class="hljs-comment">// 判断是变量</span>
<span class="hljs-keyword">if</span> (tree.<span class="hljs-title function_">getKind</span>().<span class="hljs-title function_">equals</span>(<span class="hljs-title class_">Tree</span>.<span class="hljs-property">Kind</span>.<span class="hljs-property">VARIABLE</span>)) {
<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCVariableDecl</span> jcVariableDecl = (<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCVariableDecl</span>) tree;
<span class="hljs-comment">// 此处能用com.sun.tools.javac.util.List的add方法</span>
jcVariableDeclList = jcVariableDeclList.<span class="hljs-title function_">append</span>(jcVariableDecl);
<span class="hljs-title function_">log</span>(<span class="hljs-string">"已读取字段="</span> + jcVariableDecl.<span class="hljs-title function_">getName</span>().<span class="hljs-title function_">toString</span>());
}
}
<span class="hljs-keyword">for</span> (<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCVariableDecl</span> jcVariableDecl : jcVariableDeclList) {
<span class="hljs-comment">// 追加类里面的定义</span>
jcClassDecl.<span class="hljs-property">defs</span> = jcClassDecl.<span class="hljs-property">defs</span>.<span class="hljs-title function_">append</span>(<span class="hljs-title function_">buildGetterMethod</span>(jcVariableDecl));
jcClassDecl.<span class="hljs-property">defs</span> = jcClassDecl.<span class="hljs-property">defs</span>.<span class="hljs-title function_">append</span>(<span class="hljs-title function_">buildSetterMethod</span>(jcVariableDecl));
}
<span class="hljs-variable language_">super</span>.<span class="hljs-title function_">visitClassDef</span>(jcClassDecl);
}
});
}
<span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
<span class="hljs-comment">/***
* 构建get方法的JCTree.JCMethodDecl对象
* <span class="hljs-doctag">@param</span> <span class="hljs-variable">jcVariableDecl</span>
* <span class="hljs-doctag">@return</span>
*/</span>
<span class="hljs-keyword">private</span> <span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCMethodDecl</span> <span class="hljs-title function_">buildGetterMethod</span>(<span class="hljs-params">JCTree.JCVariableDecl jcVariableDecl</span>) {
<span class="hljs-comment">// 构建方法体</span>
<span class="hljs-title class_">ListBuffer</span><<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCStatement</span>> statements = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ListBuffer</span><>();
statements.<span class="hljs-title function_">append</span>(treeMaker.<span class="hljs-title class_">Return</span>(treeMaker.<span class="hljs-title class_">Select</span>(treeMaker.<span class="hljs-title class_">Ident</span>(names.<span class="hljs-title function_">fromString</span>(<span class="hljs-string">"this"</span>)), jcVariableDecl.<span class="hljs-title function_">getName</span>())));
<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCBlock</span> body = treeMaker.<span class="hljs-title class_">Block</span>(<span class="hljs-number">0</span>, statements.<span class="hljs-title function_">toList</span>());
<span class="hljs-comment">// JCTree.JCMethodDecl不能直接使用new关键字来创建,可以通过treeMaker.MethodDef来创建</span>
<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCMethodDecl</span> jcMethodDecl = treeMaker.<span class="hljs-title class_">MethodDef</span>(
treeMaker.<span class="hljs-title class_">Modifiers</span>(<span class="hljs-title class_">Flags</span>.<span class="hljs-property">PUBLIC</span>), <span class="hljs-comment">// 访问标志</span>
<span class="hljs-title function_">generateGetMethodName</span>(jcVariableDecl.<span class="hljs-title function_">getName</span>()), <span class="hljs-comment">// 方法名</span>
jcVariableDecl.<span class="hljs-property">vartype</span>, <span class="hljs-comment">// 返回参数</span>
<span class="hljs-title class_">List</span>.<span class="hljs-title function_">nil</span>(), <span class="hljs-comment">// 泛型参数列表</span>
<span class="hljs-title class_">List</span>.<span class="hljs-title function_">nil</span>(), <span class="hljs-comment">// 参数列表</span>
<span class="hljs-title class_">List</span>.<span class="hljs-title function_">nil</span>(), <span class="hljs-comment">// 异常声明列表</span>
body, <span class="hljs-comment">// 方法体</span>
<span class="hljs-literal">null</span>
);
<span class="hljs-keyword">return</span> jcMethodDecl;
}
<span class="hljs-comment">/***
* 构建set方法的JCTree.JCMethodDecl对象
* <span class="hljs-doctag">@param</span> <span class="hljs-variable">jcVariableDecl</span>
* <span class="hljs-doctag">@return</span>
*/</span>
<span class="hljs-keyword">private</span> <span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCMethodDecl</span> <span class="hljs-title function_">buildSetterMethod</span>(<span class="hljs-params">JCTree.JCVariableDecl jcVariableDecl</span>) {
<span class="hljs-comment">// 构建方法体</span>
<span class="hljs-title class_">ListBuffer</span><<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCStatement</span>> statements = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ListBuffer</span><>();
statements.<span class="hljs-title function_">append</span>(treeMaker.<span class="hljs-title class_">Exec</span>(
<span class="hljs-comment">// this.var = var;</span>
<span class="hljs-comment">// treeMaker.Assign 赋值语句</span>
treeMaker.<span class="hljs-title class_">Assign</span>(
treeMaker.<span class="hljs-title class_">Select</span>(treeMaker.<span class="hljs-title class_">Ident</span>(names.<span class="hljs-title function_">fromString</span>(<span class="hljs-string">"this"</span>)), jcVariableDecl.<span class="hljs-title function_">getName</span>()),
treeMaker.<span class="hljs-title class_">Ident</span>(jcVariableDecl.<span class="hljs-title function_">getName</span>())
)
));
<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCBlock</span> body = treeMaker.<span class="hljs-title class_">Block</span>(<span class="hljs-number">0</span>, statements.<span class="hljs-title function_">toList</span>());
<span class="hljs-title class_">List</span><<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCVariableDecl</span>> params = <span class="hljs-title class_">List</span>.<span class="hljs-title function_">of</span>(
treeMaker.<span class="hljs-title class_">VarDef</span>(
treeMaker.<span class="hljs-title class_">Modifiers</span>(<span class="hljs-title class_">Flags</span>.<span class="hljs-property">PARAMETER</span>, <span class="hljs-title class_">List</span>.<span class="hljs-title function_">nil</span>()),
jcVariableDecl.<span class="hljs-property">name</span>, <span class="hljs-comment">// 参数名</span>
jcVariableDecl.<span class="hljs-property">vartype</span>, <span class="hljs-comment">// 类型</span>
<span class="hljs-literal">null</span> <span class="hljs-comment">// 初始值</span>
)
);
<span class="hljs-title class_">JCTree</span>.<span class="hljs-property">JCMethodDecl</span> jcMethodDecl = treeMaker.<span class="hljs-title class_">MethodDef</span>(
treeMaker.<span class="hljs-title class_">Modifiers</span>(<span class="hljs-title class_">Flags</span>.<span class="hljs-property">PUBLIC</span>), <span class="hljs-comment">// 访问标志</span>
<span class="hljs-title function_">generateSetMethodName</span>(jcVariableDecl.<span class="hljs-title function_">getName</span>()), <span class="hljs-comment">// 方法名 = setVar</span>
treeMaker.<span class="hljs-title class_">Type</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Type</span>.<span class="hljs-title class_">JCVoidType</span>()), <span class="hljs-comment">// 返回参数=void</span>
<span class="hljs-title class_">List</span>.<span class="hljs-title function_">nil</span>(), <span class="hljs-comment">// 泛型参数列表</span>
params, <span class="hljs-comment">// 参数列表</span>
<span class="hljs-title class_">List</span>.<span class="hljs-title function_">nil</span>(), <span class="hljs-comment">// 异常声明列表</span>
body, <span class="hljs-comment">// 方法体</span>
<span class="hljs-literal">null</span>
);
<span class="hljs-keyword">return</span> jcMethodDecl;
}
<span class="hljs-comment">/**
* 生成get方法的名称
*
* <span class="hljs-doctag">@param</span> <span class="hljs-variable">name</span>
* <span class="hljs-doctag">@return</span>
*/</span>
<span class="hljs-keyword">private</span> <span class="hljs-title class_">Name</span> <span class="hljs-title function_">generateGetMethodName</span>(<span class="hljs-params">Name name</span>) {
<span class="hljs-title class_">String</span> s = name.<span class="hljs-title function_">toString</span>();
<span class="hljs-keyword">return</span> names.<span class="hljs-title function_">fromString</span>(<span class="hljs-string">"get"</span> + s.<span class="hljs-title function_">substring</span>(<span class="hljs-number">0</span>, <span class="hljs-number">1</span>).<span class="hljs-title function_">toUpperCase</span>() + s.<span class="hljs-title function_">substring</span>(<span class="hljs-number">1</span>, name.<span class="hljs-title function_">length</span>()));
}
<span class="hljs-comment">/**
* 生成set方法的名称
*
* <span class="hljs-doctag">@param</span> <span class="hljs-variable">name</span>
* <span class="hljs-doctag">@return</span>
*/</span>
<span class="hljs-keyword">private</span> <span class="hljs-title class_">Name</span> <span class="hljs-title function_">generateSetMethodName</span>(<span class="hljs-params">Name name</span>) {
<span class="hljs-title class_">String</span> s = name.<span class="hljs-title function_">toString</span>();
<span class="hljs-keyword">return</span> names.<span class="hljs-title function_">fromString</span>(<span class="hljs-string">"set"</span> + s.<span class="hljs-title function_">substring</span>(<span class="hljs-number">0</span>, <span class="hljs-number">1</span>).<span class="hljs-title function_">toUpperCase</span>() + s.<span class="hljs-title function_">substring</span>(<span class="hljs-number">1</span>, name.<span class="hljs-title function_">length</span>()));
}
<span class="hljs-comment">/**
* 打印日志
*
* <span class="hljs-doctag">@param</span> <span class="hljs-variable">msg</span>
*/</span>
<span class="hljs-keyword">private</span> <span class="hljs-built_in">void</span> <span class="hljs-title function_">log</span>(<span class="hljs-params"><span class="hljs-built_in">String</span> msg</span>) {
messager.<span class="hljs-title function_">printMessage</span>(<span class="hljs-title class_">Diagnostic</span>.<span class="hljs-property">Kind</span>.<span class="hljs-property">NOTE</span>, msg);
}
}
5. 创建一个测试类
在 apt-test 下创建一个 User 类
package com.linmt.po;
import com.linmt.annotation.Data;
@Data
public class User {
private Long id;
private String name;
}
6. 通过 idea 的 maven 工具栏进行编译
点击 clean 清除编译的生成的文件,点击 compile 进行编译。
如果想要在 idea 中去断点调试可以右键 compile, 点击 debug
7. 查看编译后的结果
生成的 class 文件在 apt-test 下的 target/classes/ 下
8. 通过反射查询 User 类的方法
public class Main {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
Method[] declaredMethods = User.class.getDeclaredMethods();
<span class="hljs-keyword">for</span> (Method method: declaredMethods) {
System.out.println(method.getName());
}
}
}
//
打印的结果
setId
getName
getId
setName
三、遇到的问题
1. 编译报错一
解决方法:DataProcessor 类中增加一行代码
2. 编译报错二
解决方法:idea 中的 compiler 中配置参数 -Djps.track.ap.dependencies=false