JCTree
未走完的路
在 compile-doc-processor 中我们可以在编译时做一些事情;
在 javassist 中我们可以对 class 文件进行修改。
如果我们想在编译时直接对 class 文件进行修改,将二者结合起来可以吗?
答案是否定的,因为编译时 class 文件还没生成,javassist 的修改也就无从谈起。
如何解决这个问题?
答案之一就是 JCTree
,国内资料基本为零。学习全靠瞎摸索。
何为 JCTree
这一切可以简单从 Java 的编译原理说起,比如《深入理解 Java 虚拟机》。
大部分的书、博客也都可以从类似的书中找到。此处不赘述,直接借用:
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;
}
}