AnotationProcessor相关

前话

干活的时候,很多时候都是增删改查,重复劳动。甚至有的东西,就纯粹是一个模式,CV大法。
比如从数据库的Entity类,转换成给前端的数据,很多时候是原封不动,或者做一些简单的编辑。
那既然java的注解那么强大,为啥不琢磨琢磨整一个注解来解决这些事情呢?

原材料

tools.jar

这个在1.8以及之前的jdk包中,是可以在安装目录下找到的。
但是之后的版本中就没有了,我这里用的是java 11,但是用1.8版本的tools.jar也是没毛病的。

AbstractProcessor

这个是注解处理器,在编译时通过注解来生成一些代码,就需要用这个。比如大名鼎鼎的lombok也是通过这个实现了很多功能。

开始

AbstractProcessor

这个就是注解处理器,他有两个方法,需要我们实现。
此外,还需要配置该注解处理器感兴趣的注解以及指定java版本。
后者可以通过注解或者实现AbstractProcessor方法两种方式来实现。

配置

SupportedAnnotationTypes

配置注解处理器感兴趣的注解,这里可以通过实现

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        
    }

方法来配置。

SupportedSourceVersion

设定java版本,这里也可以通过实现

    @Override
    public SourceVersion getSupportedSourceVersion() {
        
    }

来配置。

init

获取注解处理器需要用到的一些对象。

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

process

注解处理器的关键方法,用来根据注解对编译过程就行干预。
返回值表示这个过程中,代码是否发生了修改。

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    }

举例

Import

    private void addImportInfo(Element element, String classPath) {
        int idx = classPath.lastIndexOf(".");
        String pkg = classPath.substring(0, idx), clz = classPath.substring(idx + 1);
        TreePath treePath = trees.getPath(element);
        Tree leaf = treePath.getLeaf();
        if (treePath.getCompilationUnit() instanceof JCTree.JCCompilationUnit && leaf instanceof JCTree) {
            JCTree.JCCompilationUnit jccu = (JCTree.JCCompilationUnit) treePath.getCompilationUnit();
            for (JCTree jcTree : jccu.getImports()) {
                if (jcTree != null && jcTree instanceof JCTree.JCImport) {
                    JCTree.JCImport jcImport = (JCTree.JCImport) jcTree;
                    if (jcImport.qualid != null && jcImport.qualid instanceof JCTree.JCFieldAccess) {
                        JCTree.JCFieldAccess jcFieldAccess = (JCTree.JCFieldAccess) jcImport.qualid;
                        try {
                            if (pkg.equals(jcFieldAccess.selected.toString()) && clz.equals(jcFieldAccess.name.toString())) {
                                return;
                            }
                        } catch (NullPointerException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            java.util.List<JCTree> trees = new ArrayList<>();
            trees.addAll(jccu.defs);
            JCTree.JCIdent ident = treeMaker.Ident(names.fromString(pkg));
            JCTree.JCImport jcImport = treeMaker.Import(treeMaker.Select(
                ident, names.fromString(clz)), false);
            idx = 0;
            for (JCTree tree: trees) {
                if (tree instanceof JCTree.JCPackageDecl) {
                    ++idx;
                }
            }
            if (!trees.contains(jcImport)) {
                trees.add(idx, jcImport);
            }
            jccu.defs = List.from(trees);
        }
    }

这个方法有一个问题,就是如果在代码中并没有真正使用引入的类,那么即使调用了这个方法,那编译出来的.class文件依然会把这条多余的引用语句删掉。

Getter

获取类的所有字段
    private List<JCTree.JCVariableDecl> getJCVariables(JCTree.JCClassDecl jcClass) {
        ListBuffer<JCTree.JCVariableDecl> jcVariables = new ListBuffer<>();
        //遍历jcClass的所有内部节点,可能是字段,方法等等
        for (JCTree jcTree : jcClass.defs) {
            //找出所有set方法节点,并添加
            if (isValidField(jcTree)) {
                //注意这个com.sun.tools.javac.util.List的用法,不支持链式操作,更改后必须赋值
                jcVariables.append((JCTree.JCVariableDecl) jcTree);
            }
        }
        return jcVariables.toList();
    }
生成get方法
    private JCTree.JCMethodDecl makeGetterMethodDecl(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());
        return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype, List.nil(), List.nil(), List.nil(), body, null);
    }
    private Name getNewMethodName(Name name) {
        String s = name.toString();
        return names.fromString("hello" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length()));
    }

NoArgsConstructor

    private JCTree.JCMethodDecl createNoArgsConstructor() {
        JCTree.JCBlock jcBlock = treeMaker.Block(
                0 //访问标志
                , List.nil() //所有的语句
        );
        return treeMaker.MethodDef(
                treeMaker.Modifiers(Flags.PUBLIC), //访问标志
                names.fromString(CONSTRUCTOR_NAME), //名字
                treeMaker.TypeIdent(TypeTag.VOID), //返回类型
                List.nil(), //泛型形参列表
                List.nil(), //参数列表
                List.nil(), //异常列表
                jcBlock, //方法体
                null //默认方法(可能是interface中的那个default)
        );
    }

AllArgsConstructor

    private JCTree.JCMethodDecl createAllArgsConstructor() {
        ListBuffer<JCTree.JCStatement> jcStatements = new ListBuffer<>();
        for (JCTree.JCVariableDecl jcVariable : fieldJCVariables) {
            //添加构造方法的赋值语句 " this.xxx = xxx; "
            jcStatements.append(
                    treeMaker.Exec(
                            treeMaker.Assign(
                                    treeMaker.Select(
                                            treeMaker.Ident(names.fromString(THIS)),
                                            names.fromString(jcVariable.name.toString())
                                    ),
                                    treeMaker.Ident(names.fromString(jcVariable.name.toString()))
                            )
                    )
            );
        }
        JCTree.JCBlock jcBlock = treeMaker.Block(
                0 //访问标志
                , jcStatements.toList() //所有的语句
        );
        return treeMaker.MethodDef(
                treeMaker.Modifiers(Flags.PUBLIC), //访问标志
                names.fromString(CONSTRUCTOR_NAME), //名字
                treeMaker.TypeIdent(TypeTag.VOID), //返回类型
                List.nil(), //泛型形参列表
                cloneJCVariablesAsParams(treeMaker, fieldJCVariables), //参数列表
                List.nil(), //异常列表
                jcBlock, //方法体
                null //默认方法(可能是interface中的那个default)
        );
    }

EntityToDtoConstructor

    private JCTree.JCMethodDecl createToEntityConstructor(Element element, String mappings, Element param) {
        ListBuffer<JCTree.JCVariableDecl> jcVariables = new ListBuffer<>();
        jcVariables.append(
                treeMaker.Param(names.fromString("entity"), (Type) param.asType(), null));
        ListBuffer<JCTree.JCStatement> jcStatements = new ListBuffer<>();
        for (JCTree.JCVariableDecl jcVariable: this.fieldJCVariables) {
            jcStatements.append(
                    treeMaker.Exec(
                            treeMaker.Assign(
                                    treeMaker.Select(
                                            treeMaker.Ident(names.fromString(ProcessUtil.THIS)),
                                            jcVariable.name
                                    ),
                                    treeMaker.Select(
                                            treeMaker.Ident(names.fromString("entity")),
                                            jcVariable.name
                                    )
                            )
                    )
            );
        }
        return treeMaker.MethodDef(
                treeMaker.Modifiers(Flags.PUBLIC),
                names.fromString(ProcessUtil.CONSTRUCTOR_NAME),
                treeMaker.TypeIdent(TypeTag.VOID),
                List.nil(),
                jcVariables.toList(),
                List.nil(),
                treeMaker.Block(0, jcStatements.toList()),
                null
        );
    }

尾声

一些问题

1.8以上java版本如何使用tools.jar

    <dependency>
        <groupId>com.sun</groupId>
        <artifactId>tools</artifactId>
        <version>1.8</version>
        <scope>system</scope>
        <systemPath>${project.basedir}/src/lib/tools.jar</systemPath>
    </dependency>

auto-service

在注解处理器的依赖中,最好增加这个依赖,他会自动生成javax.annotation.processing.Processor文件。

怎么调试注解处理器

idea右上角Edit Configurations...处,新增一个Remote JVM Debug,端口5005默认即可。 如图所示:
在这里插入图片描述

然后两下上档键,输入enable build process,置为ON,如图所示:
在这里插入图片描述

然后加上断点,正常启动项目,并debug启动新增的remote debug即可进入断点调试。

关于idea版本

新版idea对注解处理器的支持似乎有一些问题。需要在Perferences -> Build, Execution, Deployment -> Compiler -> Shared builid process VM options
处填上-Djps.track.ap.dependencies=false
在这里插入图片描述

完整代码

GITHUB

BLOG

参考

Lombok原理分析与功能实现

Java-JSR-269-插入式注解处理器

关于ast抽象语法树JcImport和JCCompilationUnit的用法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值