MacBook Pro
Java:1.8
gradle:5.5.1
公司封装了一个@slf4j,用于日志打印;
最近研究了下这个注解,了解到了AST,抽象树这个概念;
明白了lombok这样的插件也是利用这个技术来实现的;
在研究过程中,被下面这个错卡了很久:
错误: 程序包com.sun.source.tree不存在
import com.sun.source.tree.Tree;
编程语言有很多,但是基本都是:类型、运算符、函数、对象,流程语句组成的本质;
那该如何表示这些本质性的东西呢?
答案:转化为统一结构;
这个统一的结构不依赖于源语言的语法,只代表源语言中的语法结构,如类型、修饰符、运算符、函数、对象等,
这就是抽象语法树 AST;其是源代码的抽象语法结构的树状表现形式,每一个节点代表一个语法结构。
我自己的理解为:用代码去描述代码的结构
因为有了这么一个统一的结构去描述源代码的结构,
并且JDK官方用Java代码实现了这个结构,
所以我们就可以通过自定义的方式去实现我们的一些想法。
权威参考:
《深入理解jvm虚拟机》第三版 第10章 前端编译与优化
。
项目结构
processor
|
|--src
| |--java
| |--com
| |--hello
| |--Person
|
|
|
|
|--myprocessor
|-- src
|--main
|--java
|-- com.processor
|-- MyGetterProcessor
|-- MyGetter
再点下一步后,再点击Finish
即可。
这个module
是专门用来存放自定义注解和自定义注解处理器的。
下一步后,再点击Finish
即可。
在myprocessor
模块中创建MyGetter
注解
package com.processor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author yutao
* @since 2020/5/9 4:46 下午
*/
@Retention(RetentionPolicy.SOURCE) // 注解只在源码中保留
@Target(ElementType.TYPE) // 用于修饰类
public @interface MyGetter {
}
package com.processor;
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.Set;
/**
* @author yutao
* @since 2020/5/9 4:47 下午
*/
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.processor.MyGetter")
public class MyGetterProcessor extends AbstractProcessor {
private Messager messager; // 编译时期输入日志的
private JavacTrees javacTrees; // 提供了待处理的抽象语法树
private TreeMaker treeMaker; // 封装了创建AST节点的一些方法
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) {
Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(MyGetter.class);
elementsAnnotatedWith.forEach(e -> {
JCTree tree = javacTrees.getTree(e);
tree.accept(new TreeTranslator() {
@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(makeGetterMethodDecl(jcVariableDecl));
});
super.visitClassDef(jcClassDecl);
}
});
});
return true;
}
private JCTree.JCMethodDecl makeGetterMethodDecl(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()), methodType, List.nil(),
parameters, List.nil(), block, null);
}
private Name getNewMethodName(Name name) {
String s = name.toString();
return names.fromString("get" + 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
)
);
}
}
虽然写了自定义处理器,但是怎样才能让jvm
认识它呢?
需要在resources
目录中添加META-INF.services
文件夹,里面创建一个javax.annotation.processing.Processor
文件;
javax.annotation.processing.Processor
这个文件里面写自定义注解处理器的全类名称,多个的话,就换行写:
com.processor.MyGetterProcessor
因为我们自定义注解和处理器是在myprocessor
模块中的:
主项目中要使用自定义注解@MyGetter
时,得在build.gradle
中添加依赖:
compile project('myprocessor')
gradle项目的话,要让编译时,使用自定义注解处理器,还得指定:
annotationProcessor project(':myprocessor')
完整:
plugins {
id 'java'
}
group 'com.misssad'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile project('myprocessor')
annotationProcessor project(':myprocessor')
testCompile group: 'junit', name: 'junit', version: '4.12'
}
package com.hello;
import com.processor.MyGetter;
/**
* @author yutao
* @since 2020/5/9 4:50 下午
*/
@MyGetter
public class Person {
private String name;
}
在IDEA的右侧,找到gradle
,在Tasks
- other
- compileJava
假设按照上面的步骤来的,不出意外,会报错:
错误: 程序包com.sun.source.tree不存在
import com.sun.source.tree.Tree;
会报上面这样的错误;
为什么会报这个错误呢?
因为在编译的时候,gradle
找不到tools.jar
。
网上有大部分解决方案是:
网上大部分做法是打开Project Settings
,然后再Libraries
中点击左上角+号,把tools.jar添加上去。
但是我这样做过,不行,没有解决掉我的问题。即依然报错。
解决办法是:
因为注解处理器是写在子模块里面的,所以在myprocessor
模块中的build.gradle
中添加:
compile files("${System.properties['java.home']}/../lib/tools.jar")
完整的:
plugins {
id 'java'
}
group 'com.misssad'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
// implementation fileTree(dir: 'lib', include: ['*.jar'])
testCompile group: 'junit', name: 'junit', version: '4.12'
compile files("${System.properties['java.home']}/../lib/tools.jar")
}
再次执行gradle
任务中的compileJava
任务,即可。
然后在build
文件夹中找到相应的class
文件,看看效果:
参考地址:
Java中实现自定义的注解处理器(Annotation Processor)
99%的程序员都在用Lombok,原理竟然这么简单?我也手撸了一个
AOP 最后一块拼图 | AST 抽象语法树 —— 最轻量级的AOP方法
Android自定义注解处理器
Gradle找不到tools.jar