欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

注解-第五篇

程序员文章站 2022-03-14 19:02:02
...

编译时注解

上篇文章说了一下编译时注解的用法和使用步骤,并写了一个Demo编译时生成本地文件,这篇文章会通过编译时注解生成一个Java类看一下编译时注解在安卓中的具体使用.

1、新建Android工程名为 AnnotationPractice,完成后,新建一个Module选择Java Library 名为 testlib.
2、在Java工程中定义注解类GenerateCode.
@Documented()
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE})
public @interface GenerateCode {
}
3、在Java工程中定义注解处理器GenerateProcessor
public class GenerateProcessor extends AbstractProcessor {
    private Messager messager; // 输入日志
    private Filer filer; // 生成文件
    private Elements elementsUtils; // Element工具

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(GenerateCode.class.getCanonicalName());
    }

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

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
        elementsUtils = processingEnvironment.getElementUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(GenerateCode.class);

        if (elements.iterator().hasNext()) {
            Element element = elements.iterator().next(); // 获取set集合的第一个元素
            if (ElementKind.CLASS == element.getKind()) {
                TypeElement typeElement = (TypeElement) element;
                final String pkgName = getPackageName(typeElement); // 获取包名
                messager.printMessage(Diagnostic.Kind.NOTE, "pkgName:" + pkgName); // 控制台输入的,没有实质意义

                final String clsName = typeElement.getSimpleName().toString() + "$GA"; // 获取简单类名

                String code = jointCode(pkgName, clsName);

                Writer writer = null;
                try {
                    JavaFileObject file = filer.createSourceFile(pkgName + "." + clsName);
                    writer = file.openWriter();
                    writer.write(code);

                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (writer != null)
                            writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }


            }
        }

        return true;
    }

    /**
     * 拼接代码
     *
     * @param pkgName 包名
     * @param clsName 类名
     * @return  拼接效果下面这样
     *
      package pkgName;

      public class clsName {
          public void print() {
             System.out.println("Hello");
          }
      }
     */
    private String jointCode(String pkgName, String clsName) {
        StringBuilder builder = new StringBuilder();
        builder.append("package ");
        builder.append(pkgName);
        builder.append(";");
        builder.append("\n\n");
        builder.append("public class ");
        builder.append(clsName);
        builder.append(" {");
        builder.append("\n");
        builder.append("\tpublic void print() {");
        builder.append("\n");
        builder.append("\t\tSystem.out.println(\"Hello\");");
        builder.append("\n");
        builder.append("\t}");
        builder.append("\n");
        builder.append("}");

        return builder.toString();
    }

    private String getPackageName(TypeElement type) {
        return elementsUtils.getPackageOf(type).getQualifiedName().toString();
    }

}
4、在main下新建resources文件夹,在resources下新建META-INF文件夹,在META-INF下新建services文件夹,在services下新建文件javax.annotation.processing.Processor,在javax.annotation.processing.Processor文件中写如文件注解器全限定名
com.example.bill.testlib.GenerateProcessor
5、编译. 如果配置好gradle环境变量,在Java Module根目录下,即testlib下使用 gradle build 命令编译,编译成功后会在testlib的build下生成jar包(AnnotationPractice/testlib/build/libs/testlib.jar).
6、在app中使用
  • 将编译好的testlib.jar拷贝到app的libs下,在app的build.gradle下dependencies中引入jar包
annotationProcessor files('libs/testlib.jar')
  • 在app中使用注解,在MainActivity上使用注解GenerateCode
package com.bill.annotationpractice;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

import com.example.bill.testlib.GenerateCode;

@GenerateCode
public class MainActivity extends AppCompatActivity {

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

    }

}
7、编译. 在app目录下使用 gradle build 命令编译,编译成功后会在app/build/generated/source/apt/debug/com.bill.annotationpractice下生成我们编写的MainActivityGAMainActivityGA就可以在app中使用了.生成的类如下
package com.bill.annotationpractice;

public class MainActivity$GA {
    public void print() {
        System.out.println("Hello");
    }
}

eg:AnnotationPractice-branch4

上面代码通过编译时生成了Java文件,但是需要手动拼接生成的代码和注解处理器的配置信息.下面介绍两个开源库开简化这两步,Google和Square开源的两个库分别用来生成注解处理器的配置信息和生成Java类的

具体使用如下
  • 在testlib的build.gradle中的dependencies下引入
dependencies {
    compile 'com.google.auto.service:auto-service:1.0-rc2'
    compile 'com.squareup:javapoet:1.7.0'
}
  • 注解处理器类上使用@AutoService(Processor.class),他是Google提供的作用是生成注解处理器的配置信息,代替上面的第4步的

  • 在注解处理器中使用javapoet中的api生成代码

    MethodSpec methodSpec = MethodSpec.methodBuilder("print")
            .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) // 方法修饰符
            .returns(void.class) // 方法返回值
            .addStatement("$T.out.println($S)", // 参数 System.class, "Hello") // 内容
            .build();
    TypeSpec typeSpec = TypeSpec.classBuilder(clsName) // 类名
            .addModifiers(Modifier.PUBLIC) // 类修饰符
            .addMethod(methodSpec) //类中的方法
            .build();
    JavaFile javaFile = JavaFile.builder(pkgName, typeSpec) // 包名和类
            .build();
    try {
        javaFile.writeTo(processingEnv.getFiler());
    } catch (IOException e) {
        e.printStackTrace();
    }

下面通过这两个库来替换上面的Demo

上面第1步和第2步不变,将第3步和第4步去掉,注解处理器改为下面
@AutoService(Processor.class)
public class GenerateProcessor extends AbstractProcessor {

    private Messager messager; // 输入日志
    private Filer filer; // 生成文件
    private Elements elementsUtils; // Element工具

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(GenerateCode.class.getCanonicalName());
    }

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

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
        elementsUtils = processingEnvironment.getElementUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(GenerateCode.class);

        if (elements.iterator().hasNext()) {
            Element element = elements.iterator().next(); // 获取set集合的第一个元素
            if (ElementKind.CLASS == element.getKind()) {
                TypeElement typeElement = (TypeElement) element;
                final String pkgName = getPackageName(typeElement); // 获取包名
                messager.printMessage(Diagnostic.Kind.NOTE, "pkgName:" + pkgName); // 控制台输入的,没有实质意义

                final String clsName = typeElement.getSimpleName().toString() + "$GA"; // 获取简单类名


                MethodSpec methodSpec = MethodSpec.methodBuilder("print")
                        .addModifiers(Modifier.PUBLIC)
                        .returns(void.class)
                        .addStatement("$T.out.println($S)", System.class, "Hello")
                        .build();
                TypeSpec typeSpec = TypeSpec.classBuilder(clsName)
                        .addModifiers(Modifier.PUBLIC)
                        .addMethod(methodSpec)
                        .build();
                JavaFile javaFile = JavaFile.builder(pkgName, typeSpec)
                        .build();
                try {
                    javaFile.writeTo(filer);
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

        return true;
    }

    private String getPackageName(TypeElement type) {
        return elementsUtils.getPackageOf(type).getQualifiedName().toString();
    }


}
还是上面第5步编译生成jar包,上面第6步在app中引入使用,第7步中编译,然后会报下面错误,就是找不到javapoet包,因为上面编译生成jar并没有将javapoet的代码打到包里,所以找不到

注解-第五篇

下面通过引用module的方式引入testlib,在app的build.gradle的dependencies中加入
implementation project(path: ':testlib')
annotationProcessor project(path: ':testlib')
然后在app下编译报错

注解-第五篇

按照错误提示在app的build.gradle的android下加入下面代码
lintOptions {
    abortOnError false
}
编译成功,在app/build/generated/source/apt/debug/com.bill.annotationpractice下生成MainActivity$GA类
package com.bill.annotationpractice;

public class MainActivity$GA {
    public void print() {
        System.out.println("Hello");
    }
}

eg:AnnotationPractice-branch5

相关标签: 注解