本文将带你实现一个一百多行代码实现的自动化插桩方案,解决组件化子模块的初始化和路由器的自动注册,支持多种类型的插桩、支持前插后插、支持插入代码的优先级设置。我们将使用编辑器的API来操作AST实现代码插桩,而非重量级的编译器(Aspectj)或者Gradle插件(ASM/Javassisit)。
第一步,定义AST注解:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface AST {
/**
* 类型 0插入点 1 需要插入的原始代码块
*/
int type();
/**
* 插桩的类型的id,支持多种需求的代码插桩
*/
int value();
/**
* 类型 为0 时 0表示前插 1表示后插
* 类型 为1 时 level表示优先级 0在最前 值越大 插入时排序的优先级越低
*/
int level();
/**
* 类型 0插入点 1 需要插入的代码块
*/
interface TYPE {
int TARGET = 0;
int SOURCE = 1;
}
/**
* 插入类型的id
*/
interface ID {
int MODULE_INIT = 0x0001;//模块初始化的插桩
int ROUTER_INIT = 0x0002;//路由注册的插桩
}
/**
* 类型 为0 时 0表示前插 1表示后插
*/
interface LEVEL {
int BEFORE = 0;
int AFTER = 1;
}
}
第二步,实现注解处理器
private Trees trees;
private HashMap<String, CodeItem> mTargets = new HashMap<>();
private HashMap<String, PriorityQueue<CodeItem>> mSources = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
trees = Trees.instance(env);
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
if (!roundEnv.processingOver()) {
roundEnv.getRootElements().stream()
.filter(it -> it.getKind() == ElementKind.CLASS)
.forEach(it -> ((JCTree) trees.getTree(it)).accept(new AOPTreeTranslator()));
} else {
mTargets.entrySet().stream()
.filter(it -> it.getValue().level == AST.LEVEL.AFTER)
.forEach(it -> mSources.get(it.getKey()).forEach(node
-> it.getValue().med.body.stats
= it.getValue().med.body.stats.appendList(node.med.body.stats)));
}
return false;
}
解读:
1、使用mSources存储插桩类型对应的插入代码块的优先级队列
2、使用mTargets存储插桩类型对应的插入点代码块
3、根据注解收集器处理被注解标注的源代码
4、把所有需要插入的代码按照优先级插入到相应的插入点
注意点:
javac的List不同于常见的list,操作方式完全颠覆你的习惯。编译器用了它自己的数据类型来实现List,而不是使用java集合框架(Java Collection Framework)。
并且有许多静态的方法,可以很方便的创建List:
l L