为啥
最近突然想看ast在java中是如何生成字节码的,随后又有个人让我改一下工程里面的日志,所以就趁手写了一个工具,来检测哪些不符合规范的日志
主要就是用到了java 运行时注解对 java ast进行扫描,看catch语句中有没有error日志, 当然基于这个可以编译期代码增加,但是这样就导致后面乱用日志,因此这个只是检测,我没加代码修改功能
abstractprocessor
java 运行时注解处理器, 主要是在javac 编译期 会调用此处理期,传入ast, 可以对ast进行一些操作或者分析,达到编译期修改生产的class的目的.
如下图所示:
javac 编译时是把代码编译成ast,后续的优化等生成字节码都是在ast上操作, 因此ast的结构是使用了Elemnt 这套来表示代码,elemnt 可以看一下这篇文章jctree 与 elemnt介绍,有助于后面的ast遍历.
@SupportedAnnotationTypes 是一个list 声明要处理的注解,如果为* 表示全部注解都行
@SupportedSourceVersion(SourceVersion.RELEASE_8) 是选择java版本,
@AutoService(Processor.class) 是谷歌的一个东西,可以自动生成一个文件也是用到了abstractprocessor, 可以看一下它的源码就只要有autoservice的注解,就在编译期生成一个文件.
@Override
这个是做一些初始化的引用,因为有private JavacTrees javacTrees;
public void init(ProcessingEnvironment processingEnv)
private TreeMaker treeMaker;等东西, 后面修改ast 的时候都能用到:
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
这个函数当声明的注解扫描到了后就可以调用此process 方法,进行具体的业务处理,
在里面主要用到
Set<? extends Element> setElemnt = roundEnv.getElementsAnnotatedWith(annotation);
JCTree jcTree = this.javacTrees.getTree(element);
获取JCTree 后面就使用JCTree 进行操作了
JCTree
JCTree 就是ast, 里面有很多的类型,使用的是Tag 和Kind 来区分它的节点的类型,因此遍历时就有很多判断是否是变量,还是接口还是表达式的, 比如JCClassDecl 这个是类节点, getKind 能返回时枚举还是抽象类,还是实例类, public Kind getKind() {
if ((mods.flags & Flags.ANNOTATION) != 0)
return Kind.ANNOTATION_TYPE;
else if ((mods.flags & Flags.INTERFACE) != 0)
return Kind.INTERFACE;
else if ((mods.flags & Flags.ENUM) != 0)
return Kind.ENUM;
else
return Kind.CLASS;
}
但是getTag 就只能返回一个 public Tag getTag() {
return CLASSDEF;
}
两套机制,看你自己如何使用就行了.
下面就是jctree的节点了
static class JCImport
JCExpression
JCClassDecl
JCMethodDecl 等的
JCTree 修改
jcTree.accept(new TreeTranslator())
jctree里面加入访问器,修改访问器的方法即可,
jcTree.accept(new TreeTranslator() {
@Override
public void visitMethodDef(JCTree.JCMethodDecl tree) {
super.visitMethodDef(tree);
}
@Override
public void visitClassDef(JCTree.JCClassDecl tree) {
getLog(tree);
super.visitClassDef(tree);
}
});
如上面代码,如果jctree是Class 就会调用visitClassDef, 如果是method 就是调用Method
但是记住修改后要调用原先的super.visitClassDef(tree); 或者其他对应的的visitDef,
修改
if (jcTree.getKind().equals(Tree.Kind.CLASS)) {
jcTree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
try {
//JCTree.JCVariableDecl jcVariableDecl=treeMaker.VarDef(treeMaker.Modifiers(1),)
JCTree.JCVariableDecl jcVariableDecl = treeMaker.VarDef(treeMaker.Modifiers(Flags.PRIVATE), names.fromString("name"), treeMaker.Ident(names.fromString("String")), treeMaker.Literal("BuXueWuShu"));
jcClassDecl.defs = jcClassDecl.defs.prepend(jcVariableDecl);
jcClassDecl.defs.forEach(s -> {
if (s.getKind().equals(Tree.Kind.VARIABLE)) {
jcClassDecl.defs = jcClassDecl.defs.prepend(createGetterMethod((JCTree.JCVariableDecl) s));
jcClassDecl.defs = jcClassDecl.defs.prepend(createSetterMethod((JCTree.JCVariableDecl) s));
}
});
super.visitClassDef(jcClassDecl);
} catch (Throwable t) {
System.out.println("error:" + t);
}
}
});
}
// 主要的函数就是 jcClassDecl.defs 的重新赋值,然后再一把super.visitClassDef(jcClassDecl); 即可.
JCTree.JCVariableDecl jcVariableDecl = treeMaker.VarDef(treeMaker.Modifiers(Flags.PRIVATE), names.fromString("name"), treeMaker.Ident(names.fromString("String")), treeMaker.Literal("BuXueWuShu"));
jcClassDecl.defs = jcClassDecl.defs.prepend(jcVariableDecl);
具体代码
package com.jia.errorlogcheck;
import com.google.auto.service.AutoService;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
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.List;
import com.sun.tools.javac.util.Names;
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;
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class CompErrorLogProcessor extends AbstractProcessor {
private Messager messager;
private JavacTrees javacTrees;
private TreeMaker treeMaker;
private Names names;
@Override
public void init(ProcessingEnvironment processingEnv) {
System.out.println("init");
try {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
this.javacTrees = JavacTrees.instance(processingEnv);
this.treeMaker = TreeMaker.instance(((JavacProcessingEnvironment) processingEnv).getContext());
this.names = Names.instance(((JavacProcessingEnvironment) processingEnv).getContext());
this.messager.printMessage(Diagnostic.Kind.NOTE, "wwwwwww");
} catch (Exception e) {
System.out.println(e);
}
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
annotations.forEach(ann -> System.out.println(ann));
annotations.forEach(annotation -> {
Set<? extends Element> setElemnt = roundEnv.getElementsAnnotatedWith(annotation);
setElemnt.forEach(element -> {
JCTree jcTree = this.javacTrees.getTree(element);
if(jcTree==null){
System.out.println(element);
return;
}
if (Tree.Kind.CLASS.equals(jcTree.getKind())) {
System.out.println(jcTree);
jcTree.accept(new TreeTranslator() {
@Override
public void visitMethodDef(JCTree.JCMethodDecl tree) {
super.visitMethodDef(tree);
}
@Override
public void visitClassDef(JCTree.JCClassDecl tree) {
getLog(tree);
super.visitClassDef(tree);
}
});
}
});
});
} catch (Throwable e) {
System.out.println("ddddd" + e);
e.printStackTrace();
}
return false;
}
public void getLog(JCTree.JCClassDecl jcClassDecl) {
jcClassDecl.defs.forEach(jcTree -> {
if (jcTree.getKind() == Tree.Kind.METHOD) {
JCTree.JCMethodDecl jcMethodDecl = (JCTree.JCMethodDecl) jcTree;
JCTree.JCBlock jcBlock = jcMethodDecl.getBody();
List<JCTree.JCStatement> stats = jcBlock.stats;
stats.forEach(stat -> {
if (stat.getTag().equals(JCTree.Tag.TRY)) {
List<JCTree.JCCatch> catches = null;
try {
JCTree.JCStatement.JCTry jcTry = (JCTree.JCStatement.JCTry) stat;
catches = jcTry.getCatches();
catches.forEach(jcCatch -> {
JCTree.JCBlock jcBlock1 = jcCatch.body;
List<JCTree.JCStatement> list = jcBlock1.getStatements();
boolean isHaveError = false;
for (int i = 0; i < list.size(); i++) {
if (list.get(i).getTag().equals(JCTree.Tag.EXEC)) {
JCTree.JCExpressionStatement expressionStatement = (JCTree.JCExpressionStatement) list.get(i);
if (expressionStatement.toString().contains(".error(")) {
isHaveError = true;
}
}
}
if (!isHaveError) {
System.out.println("jia jun long");
System.out.println(jcClassDecl.getSimpleName() + "." + jcMethodDecl.getName() + "||" + jcBlock1.pos);
System.out.println(jcBlock1);
}
/* jcBlock1.stats.forEach(catchStat -> {
JCTree.JCExpressionStatement expressionStatement = (JCTree.JCExpressionStatement) catchStat;
if(expressionStatement.toString().contains(".error()")){
}
if (expressionStatement.expr.getTag() == JCTree.Tag.APPLY) {
expressionStatement.expr
System.out.println(catchStat);
}
});*/
});
} catch (Throwable e) {
System.out.println("error:" + e);
System.out.println(catches);
}
}
});
}
});
}
}
pom 文件及依赖及代码库
因为和lombok一样,所以在编译的时候,会和它冲突因此得在pom中加一下lombok的扫描
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<!--<annotationProcessors>
<annotationProcessor>
com.jia.compannotationp.CompAnnotationProcessor
</annotationProcessor>
</annotationProcessors>-->
<annotationProcessorPaths>
<path>
<groupId>com.jia</groupId>
<artifactId>errorlogcheck</artifactId>
<version>1.1-SNAPSHOT</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</path>
<!-- <path>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.1</version>
</path>-->
</annotationProcessorPaths>
</configuration>
</plugin>
lombok
lombok 和这个是一样的原理,扫描出带有@data @slf4j的 类,使用abstractprocessor 中就是在process 函数里面 遍历变量,然后增加get和set方法,如下面的例子
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
System.out.println("processs start");
try {
Set<? extends Element> result = roundEnv.getElementsAnnotatedWith(CompAnnotationTest.class);
result.forEach(element -> {
JCTree jcTree = javacTrees.getTree(element);
if (jcTree.getKind().equals(Tree.Kind.CLASS)) {
jcTree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
try {
//JCTree.JCVariableDecl jcVariableDecl=treeMaker.VarDef(treeMaker.Modifiers(1),)
JCTree.JCVariableDecl jcVariableDecl = treeMaker.VarDef(treeMaker.Modifiers(Flags.PRIVATE), names.fromString("name"), treeMaker.Ident(names.fromString("String")), treeMaker.Literal("BuXueWuShu"));
jcClassDecl.defs = jcClassDecl.defs.prepend(jcVariableDecl);
jcClassDecl.defs.forEach(s -> {
if (s.getKind().equals(Tree.Kind.VARIABLE)) {
jcClassDecl.defs = jcClassDecl.defs.prepend(createGetterMethod((JCTree.JCVariableDecl) s));
jcClassDecl.defs = jcClassDecl.defs.prepend(createSetterMethod((JCTree.JCVariableDecl) s));
}
});
super.visitClassDef(jcClassDecl);
} catch (Throwable t) {
System.out.println("error:" + t);
}
}
});
}
});
}catch (Throwable t){
System.out.println("this is error");
System.out.println(t);
}
return false;
}
/**
* 创建getter方法的语法树节点
*
* @param def 变量节点
* @return getter方法的语法树节点
*/
private JCTree createGetterMethod(JCTree.JCVariableDecl def) {
return treeMaker.MethodDef(
// 访问修饰符
treeMaker.Modifiers(Flags.PUBLIC),
// 方法名
names.fromString("get" + this.toFirstUpperCase(def.getName().toString())),
// 方法返回类型
(JCTree.JCExpression) def.getType(),
// 泛型参数
List.nil(),
// 方法参数列表
List.nil(),
// throw表达式
List.nil(),
// 方法体
treeMaker.Block(0L, List.of(
treeMaker.Return(
treeMaker.Select(
treeMaker.Ident(names.fromString("this")),
names.fromString(def.getName().toString())
)
)
)),
null
);
}
/**
* 创建setter方法的语法树节点
*
* @param def 变量节点
* @return setter方法的语法树节点
*/
private JCTree createSetterMethod(JCTree.JCVariableDecl def) {
/* treeMaker.MethodDef(
treeMaker.Modifiers(Flags.SYNCHRONIZED + Flags.PUBLIC),
names.fromString("set"+toFirstUpperCase(def.getName().toString())),
treeMaker.TypeIdent(TypeTag.VOID),
List.nil(),
List.of(treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),def.getName(),def.vartype,null)),
List.nil(),
treeMaker.Block(0L,List.of(treeMaker.Exec(treeMaker.)))
)*/
// 构造setter方法
return treeMaker.MethodDef(
// 访问修饰符
treeMaker.Modifiers(Flags.PUBLIC),
// 方法名
names.fromString("set" + this.toFirstUpperCase(def.getName().toString())),
// 方法返回类型 或者 treeMaker.Type(new Type.JCVoidType());
treeMaker.TypeIdent(TypeTag.VOID),
// 泛型参数
List.nil(),
// 方法参数列表
List.of(
treeMaker.VarDef(
treeMaker.Modifiers(Flags.PARAMETER),
def.getName(),
def.vartype,
null
)
),
// throw表达式
List.nil(),
// 方法体
treeMaker.Block(0L,
List.of(
treeMaker.Exec(
treeMaker.Assign(
treeMaker.Select(
treeMaker.Ident(names.fromString("this")),
def.getName()
),
treeMaker.Ident(def.getName())
)
)
)
),
// 默认值
null
);
}
/**
* 工具方法:将字符串首位转为大写
*
* @param str 源字符串
* @return 首位大写的字符串
*/
private String toFirstUpperCase(String str) {
char[] charArray = str.toCharArray();
charArray[0] -= 32;
return String.valueOf(charArray);
}
发表回复