AST x Annotation(一)

前言


补上上个月就该写的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<String> 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<MyGetter>{

    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<JCTree.JCStatement> statements = new ListBuffer<>();
        statements.append(jcReturn);
        JCTree.JCBlock block = treeMaker.Block(Flags.BLOCK, statements.toList());

        statements.clear();

        List<JCTree.JCTypeParameter> typeParameters = new ListBuffer<JCTree.JCTypeParameter>().toList();
        List<JCTree.JCVariableDecl> variableDecls = new ListBuffer<JCTree.JCVariableDecl>().toList();
        List<JCTree.JCExpression> throws1 = new ListBuffer<JCTree.JCExpression>().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

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值