补上上个月就该写的Annotation~,整体介绍如何用 AST加Annotation 改变原有代码。
AST即abstract syntax tree,javac的抽象语法树,对于Eclipse来说Eclipse有专门的编译器ECJ(Eclipse Compiler for Java)。本文主要用了JSR 269 API 开发,JSR 269使得AST x Annotation成了一个开发可能。
1.整体项目是AS编写,主要为了实现一个自动读取配置和生成一个getter函数,目录结构如下:
其中app为主项目,app.annotation为Annotation模块,app.ext为处理器模块创建时选java library即可。为什么要分三个Module?主要考虑到编译配置时互相重复引用的问题,故分开。后续会打包处理成一个jar包。
app.ext需要用到javac api,需要导入tools.jar,tools.jar在JDK/lib 目录下面即%JAVA_HOME%\lib\tools.jar,各个项目Gradle配置如下:
// app dependence
compile project(path: ':app.annotation')
annotationProcessor project(':app.ext')
// app.annotation 默认java项目即可
// app.ext dependence 需要导入tools.jar
compile project(path: ':app.annotation')
compile files('libs/tools.jar')
2.app.annotation
Annotation模块主要为Annotation定义,这里定义两个注解,@FunctionManager处理配置读取,@Getter对应自动生成getter函数。两个注解Retention选择RetentionPolicy.SOURCE,因为我们是在编译期处理生成class之前需要,生成class之后我们就不需要了。Targer这里选择ElementType.FIELD,也就是类成员变量,而ElementType.LOCAL_VARIABLE对应局部变量。
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.FIELD})
public @interface FunctionManager {
String value() default "";
}
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface MyGetter {
String value() default "";
}
3.app
主项目中在需要调用的地方加上注解即可。
//user类 代码
public class User {
@MyGetter
private String name = "milo king";
@MyGetter
private boolean isMilo = true;
}
//Main类 代码
public class MainActivity extends Activity {
@FunctionManager("function_state")
private String string;
@FunctionManager("function_log_state")
private boolean isOpen;
@FunctionManager("function_str_arr")
private String[] strings;
@FunctionManager("function_int")
private int intTest;
@FunctionManager("function_int_arr")
private int[] ints;
private TextView tvShow;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvShow = findViewById(R.id.tv_show);
User user = new User();
if (user.IsMilo()) {
tvShow.setText(user.getName() + "\n" + getShowString());
}
}
private String getShowString() {
StringBuilder sb = new StringBuilder();
sb.append("string:").append(string)
.append("\nisOpen:").append(isOpen)
.append("\nstrings:");
if (strings != null) {
for (String s : strings) {
sb.append(s).append(" ");
}
}
sb.append("\nint:").append(intTest)
.append("\nints:");
if (ints != null) {
for (int i : ints) {
sb.append(i).append(" ");
}
}
return sb.toString();
}
}
4.app.ext
在app的编译配置中我们使用了annotationProcessor project(':app.ext'),意思为是在javac编译阶段调用app.ext项目,具体可以见下图(Javac编译流程),这样并不会吧app.ext的代码编译进app中。要使用annotationProcessor Gradle版本需高于2.2版本,在以前可以使用apt来代替。
具体项目配置需要定义一个Service文件,并且在Service文件(即javax.annotation.processing.Processor)中配置需要调用的类。这里META-INF/services/javax.annotation.processing.Processor中配置
com.milog.lombok.MyAnnotationProcessor,整体项目目录如下。
真正的处理,先要定义一个类继承AbstractProcessor,需要重写init、process、getSupportedAnnotationTypes 、getSupportedSourceVersion四个方法,init为初始化函数可以在此次获取JavacTrees和TreeMaker对象,这两个便是控制AST的对象。process处理函数,getSupportedAnnotationTypes过滤要处理的Annotation,getSupportedSourceVersion支持的Java版本必须重写,否则无法被调用。
我们这里用MyAnnotationProcessor作为入口处理类,即上面Service文件中配置的类名,将正真的处理留给后续的MiloProcessor,Log类为Messager类的封装,在MyApp中初始化,Messager类为Javac的日志函数,简单调试需要,复杂的调试可以搜AnnotationProcessor 调试。
public class MyAnnotationProcessor extends AbstractProcessor {
private MiloProcessor processor;
private Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
processor = new MiloProcessor();
processor.init(processingEnv);
messager = processingEnv.getMessager();
MyApp.init(messager);
}
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
MyApp.destroy();
return false;
}
Log.print(Diagnostic.Kind.NOTE, "" + JavaCompiler.version());
processor.process(annotations, roundEnv);
return true;
}
@Override
public Set getSupportedAnnotationTypes() {
return MyApp.getAnnotations();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
4.1 JCTree API
简单介绍下几个API,JCTree即AST实例,JCTree.JCCompilationUnit 为整个类的对象,包括包名,import等。JCTree.JCClassDecl 为具体类的对象,包含子类。JCTree.JCVariableDecl 为成员变量对象,JCTree.JCMethodDecl 为函数对象,还有JCTree.JCModifiers,JCTree.JCReturn,JCTree.JCStatement等。
4.2 @MyGetter
接下来开始实现@MyGetter,先想一下一个getter函数组成是什么,访问修饰符应是public,返回类型应是同数据类型,即public Object getObject() { return obejct }的形式。
public class GetterHandler extends JavacAnnotationHandler{
private final String TAG = "GetterHandler";
public GetterHandler(Context context) {
super(context);
}
@Override
public void handle(TreeMaker treeMaker, JavacNode node) {
JCTree.JCMethodDecl methodDecl = createGetterMethod(treeMaker, node.variableDecl);
Log.print(TAG + " " +methodDecl.getName().toString());
node.classDecl.defs = node.classDecl.defs.prepend(methodDecl);
}
public JCTree.JCMethodDecl createGetterMethod(TreeMaker treeMaker, JCTree.JCVariableDecl variableDecl) {
Name methodName = getMethodName(variableDecl.name, variableDecl.vartype.type);
JCTree.JCReturn jcReturn = treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), variableDecl.getName()));
JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
ListBuffer statements = new ListBuffer<>();
statements.append(jcReturn);
JCTree.JCBlock block = treeMaker.Block(Flags.BLOCK, statements.toList());
statements.clear();
List typeParameters = new ListBuffer().toList();
List variableDecls = new ListBuffer().toList();
List throws1 = new ListBuffer().toList();
return treeMaker.MethodDef(modifiers, methodName, variableDecl.vartype, typeParameters, variableDecls, throws1, block, null);
}
private Name getMethodName(Name name, Type type) {
String s = name.toString();
String get = "";
if (type.hasTag(TypeTag.BOOLEAN)) {
if (s.startsWith("is")) {
get = s.substring(0, 1).toUpperCase() + s.substring(1, name.length());
}
}else {
get = "get" + s.substring(0, 1).toUpperCase() + s.substring(1, name.length());
}
return names.fromString(get);
}
}
4.2 @FunctionManger
要实现自动读取配置,那必然要获取获取app所在环境Context对象,那么App的Application对象自然是最好的选择。这里在BaseConfig类中配置了如何获取app项目的application对象。然后在FunctionManagerHandler中进一步处理。代码过长不再贴了。
1. Lombok
2.JavaCompiler