JCTree

未走完的路

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

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

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

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

如何解决这个问题?

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

何为 JCTree

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

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

程序编译与代码优化

java-compile-flow

JCTree 就是语法树。

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

Quick Start

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

代码地址:paradise

  • Code Struct
  [plaintext]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
│ 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]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?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

我们需要解析的注解:

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** * 当工具类添加此注解。 * 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 配合使用,内容:

  [plaintext]
1
com.ryo.paradise.core.processor.UtilProcessor
  • JCTrees.java

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

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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

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

  [java]
1
2
3
@Util public class UtilTest { }

直接运行

  [plaintext]
1
$ mvn clean install

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

  [java]
1
2
3
4
public final class UtilTest { private UtilTest() { } }

TODO

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

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

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

UtilProcessor.java

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

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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; } }