JCTree

未走完的路

compile-doc-processor 中我们可以在编译时做一些事情;

javassist 中我们可以对 class 文件进行修改。

如果我们想在编译时直接对 class 文件进行修改,将二者结合起来可以吗?

答案是否定的,因为编译时 class 文件还没生成,javassist 的修改也就无从谈起。

如何解决这个问题?

答案之一就是 JCTree,国内资料基本为零。学习全靠瞎摸索。

何为 JCTree

这一切可以简单从 Java 的编译原理说起,比如《深入理解 Java 虚拟机》。

大部分的书、博客也都可以从类似的书中找到。此处不赘述,直接借用:

程序编译与代码优化

java-compile-flow

JCTree 就是语法树。

我们这一步,就可以达到在编译时对 class 文件修改的目的。

Quick Start

为了更方便的使用,建议使用 mvn 项目。

代码地址:paradise

  • Code Struct
│  pom.xml
│
│
├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─ryo
│  │  │          └─paradise
│  │  │              └─core
│  │  │                  │  readme.md
│  │  │                  │
│  │  │                  ├─annotation
│  │  │                  │      Util.java
│  │  │                  │
│  │  │                  ├─processor
│  │  │                  │      UtilProcessor.java
│  │  │                  │
│  │  │                  └─util
│  │  │                          JcTrees.java
│  │  │
│  │  └─resources
│  │      └─META-INF
│  │          └─services
│  │                  javax.annotation.processing.Processor
  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>paradise</artifactId>
        <groupId>com.ryo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>paradise-core</artifactId>


    <dependencies>
    
        <dependency>
            <groupId>sun.jdk</groupId>
            <artifactId>tools</artifactId>
            <version>1.5.0</version>
            <scope>system</scope>
            <systemPath>${java.home}/../lib/tools.jar</systemPath>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!--compiler plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${compiler.level}</source>
                    <target>${compiler.level}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                    <compilerArgument>-proc:none</compilerArgument>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
  • Util.java

我们需要解析的注解:

/**
 * 当工具类添加此注解。
 * 1. 将其 constructor 默认 private{};
 * 2. 将当前类设置为 final;
 *
 * 不足之处:
 * 1. 对于 java 类,如果是直接声明私有构造器,则 new XXX() 直接提示错误
 * 但是如果使用编译时异常,需要运行时才会报错。
 * Created by bbhou on 2017/9/29.
 */
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
@Inherited
public @interface Util {
}
  • UtilProcessor.java

这个核心代码,内部较多。将放到本博客的最后。编译时运行的代码。和

javax.annotation.processing.Processor 配合使用,内容:

com.ryo.paradise.core.processor.UtilProcessor
  • JCTrees.java

简单封装的工具类,比较粗糙:

package com.ryo.paradise.core.util;

import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.util.List;

import javax.lang.model.element.Modifier;
import java.util.Set;

/**
 * Created by bbhou on 2017/10/12.
 */
public final class JcTrees {

    /**
     * 构造器名称
     */
    public static final String CONSTRUCTOR_NAME = "<init>";


    /**
     * 是否为构造器
     * @param jcMethodDecl
     * @return
     */
    public static boolean isConstructor(JCTree.JCMethodDecl jcMethodDecl) {
        String name = jcMethodDecl.name.toString();
        if(CONSTRUCTOR_NAME.equals(name)) {
            return true;
        }
        return false;
    }

    /**
     * 是否为共有方法
     * @param jcMethodDecl
     * @return
     */
    public static boolean isPublicMethod(JCTree.JCMethodDecl jcMethodDecl) {
        JCTree.JCModifiers jcModifiers = jcMethodDecl.getModifiers();
        Set<Modifier> modifiers =  jcModifiers.getFlags();
        if(modifiers.contains(Modifier.PUBLIC)) {
            return true;
        }
        return false;
    }

    /**
     * 是否为私有方法
     * @param jcMethodDecl
     * @return
     */
    public static boolean isPrivateMethod(JCTree.JCMethodDecl jcMethodDecl) {
        JCTree.JCModifiers jcModifiers = jcMethodDecl.getModifiers();
        Set<Modifier> modifiers =  jcModifiers.getFlags();
        if(modifiers.contains(Modifier.PRIVATE)) {
            return true;
        }
        return false;
    }

    /**
     * 是否为无参方法
     * @param jcMethodDecl
     * @return
     */
    public static boolean isNoArgsMethod(JCTree.JCMethodDecl jcMethodDecl) {
        List<JCTree.JCVariableDecl> jcVariableDeclList = jcMethodDecl.getParameters();
        if(jcVariableDeclList == null
                || jcVariableDeclList.size() == 0) {
            return true;
        }
        return false;
    }
}
  • test

在其他模块引用此模块,进行测试:

@Util
public class UtilTest {
}

直接运行

$   mvn clean install

编译后的 class 文件内容如下:

public final class UtilTest {
    private UtilTest() {
    }
}

TODO

这个用起来很方便,但是有个不足之处。

比如:@Override 注解,当子类的名称错误时,会直接提示错误(此时还没编译)。当然这不依赖于代码,而是依赖于编译器。

所以接下来,将学习 Idea-plugin 的编写。如果有时间,也学习下 Eclipse-plugin 的编写。

UtilProcessor.java

内容较多,所以放在了最后。

package com.ryo.paradise.core.processor;

import com.ryo.paradise.core.annotation.Util;
import com.ryo.paradise.core.util.JcTrees;
import com.sun.source.tree.Tree;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Flags;
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.ElementKind;
import javax.lang.model.element.TypeElement;
import java.util.Set;

/**
 * 工具注解执行器
 *
 * @see com.ryo.paradise.core.annotation.Util 工具类注解
 * Created by bbhou on 2017/10/12.
 */
@SupportedAnnotationTypes("com.ryo.paradise.core.annotation.Util")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class UtilProcessor extends AbstractProcessor {


    private Trees trees;

    private TreeMaker treeMaker;

    private Name.Table names;

    /**
     * 初始化,获取编译环境
     *
     * @param env
     */
    @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
        trees = Trees.instance(env);
        Context context = ((JavacProcessingEnvironment) env).getContext();
        treeMaker = TreeMaker.instance(context);
        names = Names.instance(context).table;
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        System.out.println("============================================== UtilProcessor START ==============================================");
        // 处理有 @Util 注解的元素
        for (Element element : roundEnv.getElementsAnnotatedWith(Util.class)) {
            // 只处理作用在类上的注解
            if (element.getKind() == ElementKind.CLASS) {
                addPrivateConstructor(element);
                addFinalModifier(element);
            }
        }
        System.out.println("============================================== UtilProcessor END ==============================================");
        return true;
    }

    /**
     * 添加私有构造器
     *
     * @param element 拥有注解的元素
     */
    private void addPrivateConstructor(Element element) {
        JCTree tree = (JCTree) trees.getTree(element);
        tree.accept(new TreeTranslator() {

            @Override
            public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                jcClassDecl.mods = (JCTree.JCModifiers) this.translate((JCTree) jcClassDecl.mods);
                jcClassDecl.typarams = this.translateTypeParams(jcClassDecl.typarams);
                jcClassDecl.extending = (JCTree.JCExpression) this.translate((JCTree) jcClassDecl.extending);
                jcClassDecl.implementing = this.translate(jcClassDecl.implementing);

                ListBuffer<JCTree> statements = new ListBuffer<>();

                List<JCTree> oldList = this.translate(jcClassDecl.defs);
                boolean hasPrivateConstructor = false;  //是否拥有私有构造器

                //1. 将原来的方法添加进来
                //2. 判断是否已经有默认私有构造器
                for (JCTree jcTree : oldList) {
                    if (isPublicDefaultConstructor(jcTree)) {
                        continue;   //不添加共有默认构造器
                    }
                    if (isPrivateDefaultConstructor(jcTree)) {
                        hasPrivateConstructor = true;
                    }
                    statements.append(jcTree);
                }

                if (!hasPrivateConstructor) {
                    JCTree.JCBlock block = treeMaker.Block(0L, List.<JCTree.JCStatement>nil()); //代码方法内容
                    JCTree.JCMethodDecl constructor = treeMaker.MethodDef(
                            treeMaker.Modifiers(Flags.PRIVATE, List.<JCTree.JCAnnotation>nil()),
                            names.fromString(JcTrees.CONSTRUCTOR_NAME),
                            null,
                            List.<JCTree.JCTypeParameter>nil(),
                            List.<JCTree.JCVariableDecl>nil(),
                            List.<JCTree.JCExpression>nil(),
                            block,
                            null);

                    statements.append(constructor);
                    jcClassDecl.defs = statements.toList(); //更新
                }

                this.result = jcClassDecl;
            }
        });
    }

    /**
     * 添加 final 修饰符
     * 1. 将工具类的修饰符定义为: public final;
     *
     * @param element 拥有注解的元素
     */
    private void addFinalModifier(Element element) {
        JCTree tree = (JCTree) trees.getTree(element);
        tree.accept(new TreeTranslator() {
            @Override
            public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                jcClassDecl.mods = treeMaker.Modifiers(Flags.PUBLIC | Flags.FINAL, List.<JCTree.JCAnnotation>nil());
            }
        });
    }


    /**
     * 是否为私有默认构造器
     *
     * @param jcTree
     * @return
     */
    private boolean isPrivateDefaultConstructor(JCTree jcTree) {

        if (jcTree.getKind() == Tree.Kind.METHOD) {
            JCTree.JCMethodDecl jcMethodDecl = (JCTree.JCMethodDecl) jcTree;
            if (JcTrees.isConstructor(jcMethodDecl)
                    && JcTrees.isNoArgsMethod(jcMethodDecl)
                    && JcTrees.isPrivateMethod(jcMethodDecl)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 是否为共有默认构造器
     *
     * @param jcTree
     * @return
     */
    private boolean isPublicDefaultConstructor(JCTree jcTree) {

        if (jcTree.getKind() == Tree.Kind.METHOD) {
            JCTree.JCMethodDecl jcMethodDecl = (JCTree.JCMethodDecl) jcTree;
            if (JcTrees.isConstructor(jcMethodDecl)
                    && JcTrees.isNoArgsMethod(jcMethodDecl)
                    && JcTrees.isPublicMethod(jcMethodDecl)) {
                return true;
            }
        }

        return false;
    }
}