编译时动态生成代码技术之注解处理器(三)

概念

注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具。在源代码编译阶段,通过注解处理器,我们可以获取源文件内注解(Annotation)相关内容。
APT(Annotation Process Tool),是一种在代码编译时处理注解,按照一定的规则,生成相应的java文件,多用于对自定义注解的处理,对运行时的性能影响很小。

用途

由于注解处理器可以在程序编译阶段工作,所以我们可以在编译期间通过注解处理器进行我们需要的操作。比较常用的用法就是在编译期间获取相关注解数据,然后动态生成.java源文件(让机器帮我们写代码),通常是自动产生一些有规律性的重复代码,解决了手工编写重复代码的问题,大大提升编码效率。

注解处理器可以生成Java代码,这些生成的Java代码会组成 .java 文件,但不能修改已经存在的Java类(即不能向已有的类中添加方法)。而这些生成的Java文件,会同时与其他普通的手写Java源代码一起被javac编译。

抽象处理器 AbstractProcessor

每一个注解处理器都要继承于AbstractProcessor

import javax.annotation.processing.AbstractProcessor;

public class MyAnnotationProcessor extends AbstractProcessor
{
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment)
    {
        super.init(processingEnvironment);
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
    {
        return false;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes()
    {
        return super.getSupportedAnnotationTypes();
    }

    @Override
    public SourceVersion getSupportedSourceVersion()
    {
        return super.getSupportedSourceVersion();
    }
}
  • init(ProcessingEnvironment processingEnvironment): 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。
    ProcessingEnviroment提供很多有用的工具类Elements,TypesFiler
  • process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment): 这相当于每个处理器的主函数main()。在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。
    输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。

注意:process()函数中不能直接进行异常抛出,否则的话,运行Annotation Processor的进程会异常崩溃,然后弹出一大堆让人捉摸不清的堆栈调用日志显示.

  • getSupportedAnnotationTypes(): 这里必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称,完整的包名+类名。
    在这里定义你的注解处理器注册到哪些注解上。
  • getSupportedSourceVersion(): 用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。然而,如果你有足够的理由只支持Java 7的话,你也可以返回SourceVersion.RELEASE_7,推荐使用前者。

在Java 7以后,你也可以使用注解来代替getSupportedAnnotationTypes()和getSupportedSourceVersion()

@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("baijunyu.com.test_annotation.AAAA")
@AutoService(Processor.class)
public class TestProcessor extends AbstractProcessor {
  • @AutoService(Processor.class) :向javac注册我们这个自定义的注解处理器,这样,在javac编译时,才会调用到我们这个自定义的注解处理器方法。
    AutoService这里主要是用来生成 META-INF/services/javax.annotation.processing.Processor文件的。如果不加上这个注解,那么,你需要自己进行手动配置进行注册。手动注册的方式这里不再阐述,建议直接采用@AutoService(Processor.class)进行自定义注解处理器注册,简洁方便

Gradle引入方式:

implementation 'com.google.auto.service:auto-service:1.0-rc4'

基础工具

在init()中获得如下引用:

    private Logger mLogger;
    private Elements mElementUtils;
    private Filer mFiler;
    private Types mTypeUtils;
	@Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mTypeUtils = processingEnv.getTypeUtils();
        mLogger = new Logger(processingEnvironment.getMessager());
        mElementUtils = processingEnvironment.getElementUtils();
        mFiler = processingEnvironment.getFiler();
    }
  • Elements:一个用来处理Element的工具类
  • Types:一个用来处理TypeMirror的工具类
  • Filer :使用Filer你可以创建文件
  • Messager:用于编译时在打印日志信息,在gradle console

在Android Studio使用Annotation Processor

由于Android平台是基于OpenJDK的,而OpenJDK中不包含Annotation Processor的相关代码。因此,在使用Annotation Processor时,必须在新建Module时选择Java Library,处理注解相关的代码都需要在Java Library模块下完成。

整个项目的结构

annotation模块(Java Library) 该模块存放的是我们自定义的注解,是一个Java Library
compiler模块 (Java Library) 依赖annotation模块,处理注解并自动生成代码等,同样也是Java Library
app (Android App) 依赖compiler模块,需要使用annotationProcessor依赖compiler模块

一个简单的例子:

1.创建annotation Module,类型为Java Library,在此模块的定义我自定义注解类
在annotation中自定义一个注解

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
 int value() default -1;
}

2.创建compiler Module,类型为Java Library,在此模块中定义注解处理器
gradle配置:

  • 依赖google.auto.service
 implementation 'com.google.auto.service:auto-service:1.0-rc4'
  • 依赖annotation
implementation project(':test-annotation')

创建一个自定义Annotation Processor继承于AbstractProcessor

@AutoService(Processor.class)
public class MyAnnotationProcessor extends AbstractProcessor {

    private Filer mFiler;
    private Messager mMessager;
    private Elements mElementUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFiler = processingEnvironment.getFiler();
        mMessager = processingEnvironment.getMessager();
        mElementUtils = processingEnvironment.getElementUtils();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(BindView.class.getCanonicalName());
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> bindViewElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        for (Element element : bindViewElements) {
            //1.获取包名
            PackageElement packageElement = mElementUtils.getPackageOf(element);
            String pkName = packageElement.getQualifiedName().toString();
            note(String.format("package = %s", pkName));

            //2.获取包装类类型
            TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
            String enclosingName = enclosingElement.getQualifiedName().toString();
            note(String.format("enclosindClass = %s", enclosingElement));


            //因为BindView只作用于filed,所以这里可直接进行强转
            VariableElement bindViewElement = (VariableElement) element;
            //3.获取注解的成员变量名
            String bindViewFiledName = bindViewElement.getSimpleName().toString();
            //3.获取注解的成员变量类型
            String bindViewFiledClassType = bindViewElement.asType().toString();

            //4.获取注解元数据
            BindView bindView = element.getAnnotation(BindView.class);
            int id = bindView.value();
            note(String.format("%s %s = %d", bindViewFiledClassType, bindViewFiledName, id));

            //4.生成文件
            createFile(enclosingElement, bindViewFiledClassType, bindViewFiledName, id);
            return true;
        }
        return false;
    }

    private void createFile(TypeElement enclosingElement, String bindViewFiledClassType, String bindViewFiledName, int id) {
        String pkName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
        try {
            JavaFileObject jfo = mFiler.createSourceFile(pkName + ".ViewBinding", new Element[]{});
            Writer writer = jfo.openWriter();
            writer.write(brewCode(pkName, bindViewFiledClassType, bindViewFiledName, id));
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private String brewCode(String pkName, String bindViewFiledClassType, String bindViewFiledName, int id) {
        StringBuilder builder = new StringBuilder();
        builder.append("package " + pkName + ";\n\n");
        builder.append("//Auto generated by apt,do not modify!!\n\n");
        builder.append("public class ViewBinding { \n\n");
        builder.append("public static void main(String[] args){ \n");
        String info = String.format("%s %s = %d", bindViewFiledClassType, bindViewFiledName, id);
        builder.append("System.out.println(\"" + info + "\");\n");
        builder.append("}\n");
        builder.append("}");
        return builder.toString();
    }


    private void note(String msg) {
        mMessager.printMessage(Diagnostic.Kind.NOTE, msg);
    }

    private void note(String format, Object... args) {
        mMessager.printMessage(Diagnostic.Kind.NOTE, String.format(format, args));
    }

}

注:借助Messager,我们可以在编译时在 gradle console 输出日志.

3.使用注解

app gradle配置:

annotationProcessor project(':test-compiler ')
implementation project(':test-annotation')

在app Module中使用注解

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv)
    TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

rebuild一下,可以在Gradle Console窗口中看到打印结果:
在这里插入图片描述据注解获取到的数据还生成了一个java文件:
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值