java abstractprocessor及扫描catch 中没有error日志的代码

为啥

最近突然想看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
public void init(ProcessingEnvironment processingEnv)
这个是做一些初始化的引用,因为有private JavacTrees javacTrees;
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);
    }



评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注