本篇涉及到的所有代码都在github上面有 在网上关于如何修改Java的抽象语法树的相关API文档并不多,于是本篇记录一下相关的知识点,以便随后查阅。
1 | |
本篇涉及到的所有代码都在github上面有
在网上关于如何修改Java的抽象语法树的相关API文档并不多,于是本篇记录一下相关的知识点,以便随后查阅。
一、Java中的屠龙之术
1.1 JCTree的介绍
JCTree是语法树元素的基类,包含一个重要的字段pos,该字段用于指明当前语法树节点(JCTree)在语法树中的位置,因此我们不能直接用new关键字来创建语法树节点,即使创建了也没有意义。此外,结合访问者模式,将数据结构与数据的处理进行解耦,部分源码如下:
1 | |
我们可以看到JCTree是一个抽象类,这里重点介绍几个JCTree的子类
JCStatement:声明语法树节点,常见的子类如下
JCBlock:语句块语法树节点
JCReturn:return语句语法树节点
JCClassDecl:类定义语法树节点
JCVariableDecl:字段/变量定义语法树节点
JCMethodDecl:方法定义语法树节点
JCModifiers:访问标志语法树节点
JCExpression:表达式语法树节点,常见的子类如下
JCAssign:赋值语句语法树节点
JCIdent:标识符语法树节点,可以是变量,类型,关键字等等
1.2 TreeMaker介绍
TreeMaker用于创建一系列的语法树节点,我们上面说了创建JCTree不能直接使用new关键字来创建,所以Java为我们提供了一个工具,就是TreeMaker,它会在创建时为我们创建的JCTree对象设置pos字段,所以必须使用上下文相关的TreeMaker对象来创建语法树节点。
具体的API介绍可以参照,TreeMakerAPI,接下来着重介绍一下常用的几个方法。
TreeMaker.Modifiers
TreeMaker.Modifiers 方法用于创建访问标志语法树节点(JCModifiers),源码如下
1 | |
}
1 | |
}
1 | |
TreeMaker.ClassDef
TreeMaker.ClassDef用于创建类定义语法树节点(JCClassDecl),源码如下:
1 | |
Name name,
1 | |
JCExpression extending,
1 | |
name,
typarams,
extending,
implementing,
1 | |
}
mods:访问标志,可以通过
1 | |
mods:访问标志
name:方法名
restype:返回类型
params:参数列表
thrown:异常声明列表
body:方法体
1 | |
m:方法符号
mtype:方法类型。包含多种类型,泛型参数类型、方法参数类型、异常参数类型、返回参数类型。
TreeMaker.VarDef
TreeMaker.VarDef用于创建字段/变量定义语法树节点(JCVariableDecl),源码如下
1 | |
JCExpression vartype,
1 | |
}
1 | |
v.name,
1 | |
init,
1 | |
}
1 | |
TreeMaker.Return
TreeMaker.Return用于创建return语句(JCReturn),源码如下
1 | |
}
1 | |
一. TreeMaker.Select(treeMaker.Ident(names.fromString(“this”)), names.fromString(“name”));
1 | |
TreeMaker.NewClass
TreeMaker.NewClass用于创建new语句语法树节点(JCNewClass),源码如下:
public JCNewClass NewClass(JCExpression encl,
List
JCExpression clazz,
List
JCClassDecl def) {
JCNewClass tree = new JCNewClass(encl, typeargs, clazz, args, def);
}
1 | |
def:类定义
1 | |
public JCMethodInvocation Apply(List
1 | |
List<JCExpression> args) {
JCMethodInvocation tree = new JCMethodInvocation(typeargs, fn, args);
1 | |
fn:调用语句
TreeMaker.Assign
TreeMaker.Assign用户创建赋值语句语法树节点(JCAssign),源码如下:
ublic JCAssign Assign(JCExpression lhs,
JCExpression rhs) {
JCAssign tree = new JCAssign(lhs, rhs);
}
1 | |
public JCExpressionStatement Exec(JCExpression expr) {
JCExpressionStatement tree = new JCExpressionStatement(expr);
1 | |
TreeMaker.Block
TreeMaker.Block用于创建组合语句的语法树节点(JCBlock),源码如下:
public JCBlock Block(long flags,
List
JCBlock tree = new JCBlock(flags, stats);
}
1 | |
public class List extends AbstractCollection implements java.util.List {
public A head;
public List tail;
private static final List<?> EMPTY_LIST = new List
1 | |
public boolean isEmpty() {
return true;
1 | |
List(A head, List<A> tail) {
this.tail = tail;
this.head = head;
1 | |
public static <A> List<A> nil() {
return EMPTY_LIST;
1 | |
public List<A> prepend(A var1) {
return new List(var1, this);
1 | |
public List<A> append(A var1) {
return of(var1).prependList(this);
1 | |
public static <A> List<A> of(A var0) {
return new List(var0, nil());
1 | |
public static <A> List<A> of(A var0, A var1) {
return new List(var0, of(var1));
1 | |
public static <A> List<A> of(A var0, A var1, A var2) {
return new List(var0, of(var1, var2));
1 | |
public static <A> List<A> of(A var0, A var1, A var2, A... var3) {
return new List(var0, new List(var1, new List(var2, from(var3))));
1 | |
1.4 com.sun.tools.javac.util.ListBuffer
由于 com.sun.tools.javac.util.List 使用起来不方便,所以又在其上面封装了一层,这个封装类是 ListBuffer ,此类的操作和我们平时经常使用的 java.util.List 用法非常类似。
public class ListBuffer extends AbstractQueue {
public static <T> ListBuffer<T> of(T x) {
ListBuffer
lb.add(x);
return lb;
}
/** The list of elements of this buffer.
*/
private List<A> elems;
/** A pointer pointing to the last element of 'elems' containing data,
* or null if the list is empty.
*/
private List<A> last;
/** The number of element in this buffer.
*/
private int count;
/** Has a list been created from this buffer yet?
*/
private boolean shared;
/** Create a new initially empty list buffer.
*/
public ListBuffer() {
clear();
}
/** Append an element to buffer.
*/
public ListBuffer<A> append(A x) {
x.getClass(); // null check
if (shared) copy();
List newLast = List.of(x);
if (last != null) {
last.tail = newLast;
last = newLast;
} else {
elems = last = newLast;
}
count++;
return this;
}
……..
}
1 | |
这个是为我们创建名称的一个工具类,无论是类、方法、参数的名称都需要通过此类来创建。它里面经常被使用到的一个方法就是 fromString() ,一般使用方法如下所示。
Names names = new Names()
names. fromString(“setName”);
1.6 实战演练
上面我们大概了解了如何操作抽象语法树,接下来我们就来写几个真实的案例加深理解。
变量相关
在类中我们经常操作的参数就是变量,那么如何使用抽象语法树的特性为我们操作变量呢?接下来我们就将一些对于变量的一些操作。
生成变量
例如生成 private String age; 这样一个变量,借用我们上面讲的 VarDef 方法
// 生成参数 例如:private String age;
treeMaker.VarDef(treeMaker.Modifiers(Flags.PRIVATE), names.fromString(“age”), treeMaker.Ident(names.fromString(“String”)), null);
1 | |
例如我们想生成 private String name = "BuXueWuShu" ,还是利用 VarDef 方法
// private String name = “BuXueWuShu”
treeMaker.VarDef(treeMaker.Modifiers(Flags.PRIVATE),names.fromString(“name”),treeMaker.Ident(names.fromString(“String”)),treeMaker.Literal(“BuXueWuShu”))
两个字面量相加
例如我们生成 String add = "a" + "b"; ,借用我们上面讲的 Exec 方法和 Assign 方法
// add = “a”+”b”
treeMaker.Exec(treeMaker.Assign(treeMaker.Ident(names.fromString(“add”)),treeMaker.Binary(JCTree.Tag.PLUS,treeMaker.Literal(“a”),treeMaker.Literal(“b”))))
+=语法
例如我们想生成 add += "test" ,则和上面字面量差不多。
// add+=”test”
treeMaker.Exec(treeMaker.Assignop(JCTree.Tag.PLUS_ASG, treeMaker.Ident(names.fromString(“add”)), treeMaker.Literal(“test”)))
++语法
例如想生成 ++i
treeMaker.Exec(treeMaker.Unary(JCTree.Tag.PREINC,treeMaker.Ident(names.fromString(“i”))))
1 | |
public void test(){
1 | |
// 定义方法体
ListBuffer<JCTree.JCStatement> testStatement = new ListBuffer<>();
JCTree.JCBlock testBody = treeMaker.Block(0, testStatement.toList());
JCTree.JCMethodDecl test = treeMaker.MethodDef(
treeMaker.Modifiers(Flags.PUBLIC), // 方法限定值
names.fromString(“test”), // 方法名
treeMaker.Type(new Type.JCVoidType()), // 返回类型
com.sun.tools.javac.util.List.nil(),
1 | |
有参无返回值
/*
public void test2(String name){
name = “xxxx”;
}
*/
ListBuffer<JCTree.JCStatement> testStatement2 = new ListBuffer<>();
testStatement2.append(treeMaker.Exec(treeMaker.Assign(treeMaker.Ident(names.fromString(“name”)),treeMaker.Literal(“xxxx”))));
JCTree.JCBlock testBody2 = treeMaker.Block(0, testStatement2.toList());
// 生成入参
JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), names.fromString(“name”),treeMaker.Ident(names.fromString(“String”)), null);
com.sun.tools.javac.util.List<JCTree.JCVariableDecl> parameters = com.sun.tools.javac.util.List.of(param);
JCTree.JCMethodDecl test2 = treeMaker.MethodDef(
names.fromString(“test2”), // 方法名
parameters, // 入参
testBody2,
null
);
有参有返回值
1 | |
特殊的
我们学完了如何进行定义参数,如何进行定义方法,其实还有好多语句需要学习,例如如何生成new语句,如何生成方法调用的语句,如何生成if语句。j接下来我们就学习一些比较特殊的语法。
new一个对象
1 | |
null,
1 | |
null
);
1 | |
combatJCTreeMain
);
方法调用(无参)
JCTree.JCExpressionStatement exec = treeMaker.Exec(
treeMaker.Apply(
treeMaker.Select(
treeMaker.Ident(names.fromString(“combatJCTreeMain”)), // . 左边的内容
names.fromString(“test”) // . 右边的内容
1 | |
com.sun.tools.javac.util.List.nil()
1 | |
),
1 | |
)
);
if语句
1 | |
if("BuXueWuShu".equals(name)){
add = “a” + “b”;
}else{
add += “test”;
1 | |
),
1 | |
);
1 | |
apply, // if语句里面的判断语句
exec3, // 条件成立的语句
exec1 // 条件不成立的语句
);
1 | |
本文标题: Java中的屠龙之术如
发布时间: 2021年02月16日 00:00
最后更新: 2026年09月16日 05:40
原始链接: https://haoxiang.eu.org/2dc1cb3a/
版权声明: 本文著作权归作者所有,均采用CC BY-NC-SA 4.0许可协议,转载请注明出处!

