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>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
</plugin>
</plugins>
</build>
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;
// 定义需要处理的注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(Data.class.getCanonicalName());
return annotations;
}
// 版本支持
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
// 初始化
@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);
log("初始化....");
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Data.class);
for (Element element : elements) {
log("当前类=" + element.getSimpleName());
JCTree jcTree = javacTrees.getTree(element);
// 以下这段代码解决报错java.lang.AssertionError: Value of x -1
treeMaker.pos = jcTree.pos;
jcTree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
// jcClassDecl.defs为类里面的所有定义
for (JCTree tree : jcClassDecl.defs) {
// 判断是变量
if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
// 此处能用com.sun.tools.javac.util.List的add方法
jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
log("已读取字段=" + jcVariableDecl.getName().toString());
}
}
for (JCTree.JCVariableDecl jcVariableDecl : jcVariableDeclList) {
// 追加类里面的定义
jcClassDecl.defs = jcClassDecl.defs.append(buildGetterMethod(jcVariableDecl));
jcClassDecl.defs = jcClassDecl.defs.append(buildSetterMethod(jcVariableDecl));
}
super.visitClassDef(jcClassDecl);
}
});
}
return true;
}
/***
* 构建get方法的JCTree.JCMethodDecl对象
* @param jcVariableDecl
* @return
*/
private JCTree.JCMethodDecl buildGetterMethod(JCTree.JCVariableDecl jcVariableDecl) {
// 构建方法体
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
// JCTree.JCMethodDecl不能直接使用new关键字来创建,可以通过treeMaker.MethodDef来创建
JCTree.JCMethodDecl jcMethodDecl = treeMaker.MethodDef(
treeMaker.Modifiers(Flags.PUBLIC), // 访问标志
generateGetMethodName(jcVariableDecl.getName()), // 方法名
jcVariableDecl.vartype, // 返回参数
List.nil(), // 泛型参数列表
List.nil(), // 参数列表
List.nil(), // 异常声明列表
body, // 方法体
null
);
return jcMethodDecl;
}
/***
* 构建set方法的JCTree.JCMethodDecl对象
* @param jcVariableDecl
* @return
*/
private JCTree.JCMethodDecl buildSetterMethod(JCTree.JCVariableDecl jcVariableDecl) {
// 构建方法体
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
statements.append(treeMaker.Exec(
// this.var = var;
// treeMaker.Assign 赋值语句
treeMaker.Assign(
treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()),
treeMaker.Ident(jcVariableDecl.getName())
)
));
JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
List<JCTree.JCVariableDecl> params = List.of(
treeMaker.VarDef(
treeMaker.Modifiers(Flags.PARAMETER, List.nil()),
jcVariableDecl.name, // 参数名
jcVariableDecl.vartype, // 类型
null // 初始值
)
);
JCTree.JCMethodDecl jcMethodDecl = treeMaker.MethodDef(
treeMaker.Modifiers(Flags.PUBLIC), // 访问标志
generateSetMethodName(jcVariableDecl.getName()), // 方法名 = setVar
treeMaker.Type(new Type.JCVoidType()), // 返回参数=void
List.nil(), // 泛型参数列表
params, // 参数列表
List.nil(), // 异常声明列表
body, // 方法体
null
);
return jcMethodDecl;
}
/**
* 生成get方法的名称
*
* @param name
* @return
*/
private Name generateGetMethodName(Name name) {
String s = name.toString();
return names.fromString("get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
}
/**
* 生成set方法的名称
*
* @param name
* @return
*/
private Name generateSetMethodName(Name name) {
String s = name.toString();
return names.fromString("set" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
}
/**
* 打印日志
*
* @param msg
*/
private void log(String msg) {
messager.printMessage(Diagnostic.Kind.NOTE, 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 {
public static void main(String[] args) {
Method[] declaredMethods = User.class.getDeclaredMethods();
for (Method method: declaredMethods) {
System.out.println(method.getName());
}
}
}
//
打印的结果
setId
getName
getId
setName
三、遇到的问题
1.编译报错一
解决方法:DataProcessor类中增加一行代码
2.编译报错二
解决方法:idea中的compiler中配置参数-Djps.track.ap.dependencies=false