java 编译时注解处理

转载自fei20121106的博客

一、注解

Annotations是一种元数据,其作用在于提供程序本身以外的一些数据信息,也就是说Annotation他不会属于程序代码本身,不参与逻辑运算,故而不会对原程序代码的操作产生直接的影响。

一般来说Annotation有如下三种使用情形:

Information for the compiler — Annotations can be used by the compiler to detect errors or suppress * warnings.
Compile-time and deployment-time processing — Software tools can process annotation information to generate code, XML files, and so forth.
Runtime processing — Some annotations are available to be examined at runtime.
SOURCE 标记一些信息,为编译器提供辅助信息。

可以为编译器提供而外信息,以便于检测错误,抑制警告等,譬如@Override、@SuppressWarnings等这类注解就是用于标识,可以用作一些检验。

  • CLASS 编译时动态处理。
    一般这类注解会在编译的时候,根据注解标识,动态生成一些类或者生成一些xml都可以,在运行时期,这类注解是没有的,也就是在类加载的时候丢弃。
    会依靠动态生成的类做一些操作,因为没有反射,效率和直接调用方法没什么区别。ParcelableGenerator、butterknife 、androidannotaion都使用了类似技术

  • RUNTIME 运行时动态处理。
    这个大家见得应该最多,在运行时拿到类的Class对象,然后遍历其方法、变量,判断有无注解声明,然后做一些事情。
    譬如使用表单验证注解@Validate,不保留活动的@SaveInstance
    来看一个示例:

@Retention(RetentionPolicy.CLASS)  
@Target({ ElementType.FIELD, ElementType.TYPE })  
public @interface InjectView  
{  
    int value();  
}

在本篇中我们主要讲述的就是如何对 CLASS类的编译期注解的处理

二、注解处理器

在开始之前,我们再次申明一个非常重要的问题:我们并不讨论那些在运行时(Runtime)通过反射机制运行处理的注解,而是讨论在编译时(Compile time)处理的注解。

注解处理器是一个在javac中的,用来编译时扫描和处理的注解的工具。你可以为特定的注解,注册你自己的注解处理器。到这里,我假设你已经知道什么是注解,并且知道怎么申明的一个注解类型。

一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。

这具体的含义什么呢?

你可以生成Java代码!这些生成的Java代码是在生成的.java文件中,所以你不能修改已经存在的Java类,例如向已有的类中添加方法。这些生成的Java文件,会同其他普通的手动编写的Java源代码一样被javac编译。

三、虚处理器AbstractProcessor

我们首先看一下处理器的API。每一个处理器都是继承于AbstractProcessor,如下所示:

package com.example;

public class MyProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment env){ }

    @Override
    public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }

    @Override
    public Set<String> getSupportedAnnotationTypes() { }

    @Override
    public SourceVersion getSupportedSourceVersion() { }

}
  • init(ProcessingEnvironment env): 【核心】
    每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。
    ProcessingEnviroment提供很多有用的工具类Elements,Types和Filer。后面我们将看到详细的内容
    Filer是个接口,支持通过注解处理器创建新文件
    Elements 元素操作辅助工具类

  • process(Set< ? extends TypeElement> annotations, RoundEnvironment env):【核心】
    这相当于每个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。
    输入参数annotations 请求处理的注解类型集合
    输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素,相当于“有关全局源码的上下文环境”。后面我们将看到详细的内容。
    方法如果返回 true,则这些注解已声明并且不要求后续 Processor 处理它们;如果返回 false,则这些注解未声明并且可能要求后续 Processor 处理它们

  • getSupportedAnnotationTypes()
    这里你必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。
    换句话说,你在这里定义你的注解处理器注册到哪些注解上。

  • getSupportedSourceVersion()
    用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。
    然而,如果你有足够的理由只支持Java 6的话,你也可以返回SourceVersion.RELEASE_6。我推荐你使用前者
    在Java 7中,你也可以使用注解来代替getSupportedAnnotationTypes()和getSupportedSourceVersion(),像这样:

@SupportedSourceVersion(SourceVersion.latestSupported())
@SupportedAnnotationTypes({// 合法注解全名的集合})
public class MyProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment env){ }

    @Override
    public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
}

除此以外还有一个@SupportedOptions,这个一般是命令行时候用的,设置一些选项,but,命令行我不熟,因此:略。
注:如果大家找不到AbstractProcessor,记得右键build-path add library把jdk加进来

因为兼容的原因,特别是针对Android平台,我建议使用重载getSupportedAnnotationTypes()和getSupportedSourceVersion()方法代替@SupportedAnnotationTypes和@SupportedSourceVersion

接下来的你必须知道的事情是,注解处理器是运行它自己的虚拟机JVM中。

是的,你没有看错,javac启动一个完整Java虚拟机来运行注解处理器。这对你意味着什么?你可以使用任何你在其他java应用中使用的的东西。使用guava。如果你愿意,你可以使用依赖注入工具,例如dagger或者其他你想要的类库。但是不要忘记,即使是一个很小的处理,你也要像其他Java应用一样,注意算法效率,以及设计模式

3.1 处理器对全局代码的扫描处理流程

注解处理器对工程代码的扫描是多次的,可以注意到AbstractProcessor的process()方法的输入参数有一个是RoundEnvironment,这个代表一次扫描的结果

影响注解处理器执行顺序与逻辑的地方有三处

3.1.1 javax.annotation.processing.Processor中的书写顺序决定注册处理器的执行顺序
假设注解处理器配置文件中配置了如下注解处理器:

package.ProcessorB
package.ProcessorA

那么编译器每一轮扫描会先执行处理器ProcessorB,再执行处理器ProcessorA

3.1.2 AbstractProcessor中processor()方法的返回值决定是否要终结当前轮的处理
按照1中注册的顺序,假如ProcessorB中的process()方法返回true,则表示消费完这轮的注解扫描,将不再执行ProcessorA,只有当返回false时,才会接下来执行ProcessorA

3.1.3 没有输出文件跟输入文件时扫描结束
假设按照1中的注册顺序,ProcessorB中的process()方法返回true,并且ProcessorB在第一轮扫描会生成按文件GenerateB.java,则将在第三轮扫描后结束注解处理

也就是说针对生成的文件,还是再处理一次

过程如下

过程输入文件输出文件
第一轮原工程GenerateB.java
第二轮GenerateB.java
第三轮

第三轮时编译器发现没有输出文件也没有输入文件,处理结束
PS:每个注解处理器在整个过程中都保持同一个实例

四 语言模型包的使用

4.1 Mirror

注解处理器因为操作的是源码,所以需要用到JAVA语言模型包,javax.lang.model及其子包都是Java的语言模型包。这个包是采用了Mirror设计,Java是一种可以自描述的语言,其反射机制就是一种自描述,传统的反射机制将自描述与其他操作合并在一起,Mirror机制将自描述跟其他操作隔离,自描述部分是Meta level,其他部分是Base level

这里写图片描述

4.2 Element详解

有了注解,必然需要有一个对应的注解处理器去处理注解,但是在处理注解的时候需要充分的了解注解处理器中的process方法及时核心的编译代码,而process方法的核心便是Element对象,所以在讲解注解处理器前,需要对Element有全面的认识,方能事半功倍。

Element在逻辑上代表语言元素,比如包,类,方法等

其实Element是定义的一个接口,定义了外部调用暴露出的接口

方法解释
TypeMirror asType();返回此元素定义的类型,实际的java类型
ElementKind getKind();返回此元素的种类:包、类、接口、方法、字段…,如下枚举值,方法返回一个枚举值TypeKind
Set getModifiers()返回此元素的修饰符,如下枚举值
Name getSimpleName();返回此元素的简单名称,比如activity名,变量就是变量名
Element getEnclosingElement();返回封装此元素的最里层元素,即最近的外层元素,如果此元素的声明在词法上直接封装在另一个元素的声明中,则返回那个封装元素; 如果此元素是顶层类型,则返回它的包如果此元素是一个包,则返回 null; 如果此元素是一个泛型参数,则返回 null.
List getEnclosedElements();获取所有的内层元素
< A extends Annotation> A getAnnotation(Class< A> var1);返回此元素针对指定类型的注解(如果存在这样的注解),否则返回 null。注解可以是继承的,也可以是直接存在于此元素上的

4.2.1 转化
Element代表语言元素,比如包,类,方法等,但是Element并没有包含自身的信息,自身信息要通过Mirror来获取,每个Element都指向一个TypeMirror,这个TypeMirror里有自身的信息。通过下面获方法取Element中的Mirror

TypeMirror mirror=element.asType() 

TypeMirror类型是DeclaredType或者TypeVariable时候可以转化成Element

Types types=processingEnvironment.getTypeUtils()
Element element=types.asElement(typeMirror)

4.2.2 获取类型getKind()
获取Element或者TypeMirror的类型都是通过getKind()获取类型,但是返回值虽然不同,但是都是枚举。

ElementKind element.getKind()
TypeKind typeMirror.getKind()

针对具体类型,要避免用instanceof,因为即使同一个类getKind()也有不同的结果。比如TypeElemnt的getKind()返回结果可以是枚举,类,注解,接口四种。

enum ElementKind {
     PACKAGE,
     ENUM,
     CLASS, //类
     ANNOTATION_TYPE,
     INTERFACE, //接口
     ENUM_CONSTANT,
     FIELD, //普通的成员变量
     PARAMETER, //函数或构造函数的参数
     LOCAL_VARIABLE,//本地变量
     EXCEPTION_PARAMETER,
     METHOD,//函数
     CONSTRUCTOR,//构造函数
     STATIC_INIT,
     INSTANCE_INIT,
     OTHER,
     RESOURCE_VARIABLE;
}
enum TypeKind {
     BOOLEAN,
     BYTE,
     SHORT,
     INT,
     LONG,
     CHAR,
     FLOAT,
     DOUBLE,
     VOID,
     NONE,
     NULL,
     DECLARED, 
     ERROR,
     TYPEVAR,
     WILDCARD,
     PACKAGE,
     EXECUTABLE,
     OTHER,
     UNION,
     INTERSECTION;
}

4.2.3 Element子类
Element在逻辑上代表语言元素,比如包,类,方法等,因此也会有五个直接子接口,它们分别代表一种特定类型的元素

TablesAre
PackageElement一个包程序元素
TypeElement一个类或接口程序元素
ExecutableElement某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素
TypeParameterElement一般类、接口、方法或构造方法元素的泛型参数
VariableElement一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数

其实也正好对应了注解的@Target的作用域(详见 (2.1.19)注释与注解):

  • packages:ElementType.PACKAGE
  • types(类、接口、枚举、Annotation类型):ElementType.TYPE
  • 类型成员(方法、构造方法、成员变量、枚举值):ElementType.METHOD
  • 方法参数和本地变量(如循环变量、catch参数):ElementType.PARAMETER,ElementType.FIELD,ElementType.LOCAL_VARIABLE

五个子类各有各的用处并且有各种独立的方法,在使用的时候可以强制将Element对象转换成其中的任一一种,但是前提是满足条件的转换,不然会抛出异常

4.2.3.1 TypeElement详解
TypeElement定义的一个类或接口程序元素,相当于当前注解所在的class对象

方法解释
NestingKind getNestingKind();返回此类型元素的嵌套种类
Name getQualifiedName();返回此类型元素的完全限定名称。更准确地说,返回规范 名称。对于没有规范名称的局部类和匿名类,返回一个空名称.譬如 Activity就是包名+类名
TypeMirror getSuperclass();返回此类型元素的直接超类。如果此类型元素表示一个接口或者类 java.lang.Object,则返回一个种类为 NONE 的 NoType
List getInterfaces();返回直接由此类实现或直接由此接口扩展的接口类型
List getTypeParameters();按照声明顺序返回此类型元素的形式类型参数

4.2.3.2 VariableElement详解
VariableElement标示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数

方法解释
getConstantValue变量初始化的值
getEnclosingElement获取相关类信息
4.3 源码中移动

比如一个类里面有方法,成员变量等,这个类相对于方法跟成员变量就是外层元素,而成员变量和方法相对于类就是内层元素。
Element是代表源码的类,源码中移动到其他位置必须是用Element,比如移动到当前元素的外层元素TypeElement

public static TypeElement findEnclosingTypeElement( Element e ){
       while( e != null && !(e instanceof TypeElement) )       {
           e = e.getEnclosingElement();//通过getEnclosingElement()获取外层元素
       }
       return TypeElement.class.cast( e );
}

也可以使用getEnclosedElements()获取当前Element内层的所有元素

4.4 类继承树结构中移动

TypeMirror是用来反应类本身信息的类,在继承树移动必须用到TypeMirror,比如查找某个类的父类

Types typeUtils = processingEnv.getTypeUtils();
while (!typeElement.toString().equals(Object.class.getName())) {
        TypeMirror typeMirror=element.getSuperclass();
        element = (TypeElement)typeUtils.asElement(typeMirror);
}
4.5 处理MirroredTypeException

编译器使用注解处理器来处理目标代码时候,目标源码还没有编译成字节码,所以任何指向目标源码字节码的代码都会发生MirroredTypeException。最常见的例子见下面

//已经编译的三方jar:
@Retention(RetentionPolicy.SOURCE)
@interface What{
    Class<?> value()
}

//源码:
@What(A.class)
public class A{}

//注解处理器
public ProcessorA extends AbstractProcessor{
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        ```
        What what=typeElementA.getAnnotation(What.class);
        Class<?> clazz=what.value();  //这里将有MirroredTypeException
        ```
    }
}

注解处理器之所有MirroredTypeException,是因为此时类A还没有被编译成字节码,所以A.class不存在,解决这个问题需要异常,利用异常我能获取类A的名字等信息,但是却得不到A.class,如下

public ProcessorA extends AbstractProcessor{
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { ```What what=typeElementA.getAnnotation(What.class); try { Class<?> clazz=what.value();  //这里将有MirroredTypeException
        }catch(MirroredTypeException mte){
            TypeMirror classTypeMirror =  mte.getTypeMirror();
            TypeElement classTypeElement = (TypeElement)Types.asElement(classTypeMirro);
            //获取canonicalName
            String canonicalName= classTypeElement.getQualifiedName().toString();
            //获取simple name
            String simpleName = classTypeElement.getSimpleName().toString();
        }
       // ```
    }
}

五、示例

5.1 打印Element信息

以上的部分是不是挺懵逼?~~ 赶紧来个简单示例吧

//注解

// SOURCE也能在编译期获得,用于打印信息等
@Retention(RetentionPolicy.SOURCE)
public @interface PrintMe {
}

//注解处理器

@SupportedAnnotationTypes({"com.avenwu.annotation.PrintMe"})
public class MyProcessor extends AbstractProcessor {
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {//annotations当前处理器支持的所有注解,env全局源代码环境
        Messager messager = processingEnv.getMessager();
        for (TypeElement te : annotations) {//遍历 “当前处理器支持的所有注解”
            for (Element e : env.getElementsAnnotatedWith(te)) {//获得被该注解声明的元素 
                messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.toString()); //Gradle Console的打印
            }
        }
        return true;
    }

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

//注解使用

@PrintMe
public class MainActivity extends Activity{

 //```

 @PrintMe
 public void showDrawerFrame(```){
    //```
 }
}
5.2 生成json描述文件

定义Seriable 注解,

  • 如果类上声明注解我们就将其所有的变量都生成一个json描述文件
  • 如果仅仅是成员变量呢,那我们只提取声明的成员变量来动态生成

//目标1

public class User {  
    @Seriable  
    private String username;  
    @Seriable  
    private String password;  

    private String three;  
    private String four;  
} 

//目标二

@Seriable  
public class Article {  
    private String title ;   
    private String content ;   
}  

//预期结果1:com_zhy_User文件

{class:"com.zhy.User",  
 fields:  
 {  
  username:"java.lang.String",  
  password:"java.lang.String"  
 }  
} 

//预期结果2:com_zhy_Article文件

{class:"com.zhy.Article",  
 fields:  
 {  
  content:"java.lang.String",  
  title:"java.lang.String"  
 }  
}

基本分为两个过程:

  1. 找出标识注解的类或成员变量,封装到maps中;
  2. 遍历maps为每个类创建json文件。
@SupportedAnnotationTypes("com.zhy.annotationprocess.annotation.Seriable")  
@SupportedSourceVersion(SourceVersion.RELEASE_6)  
public class BeanProcessor extends AbstractProcessor { // 元素操作的辅助类 
    Elements elementUtils;  

    @Override  
    public synchronized void init(ProcessingEnvironment processingEnv)  
    {  
        super.init(processingEnv);  
        // 元素操作的辅助类 
        elementUtils = processingEnv.getElementUtils();  
    }  

    @Override  
    public boolean process(Set<? extends TypeElement> annotations,  
            RoundEnvironment roundEnv)  
    {  

        // 【1】获得被该注解声明的所有元素 
        Set<? extends Element> elememts = roundEnv  
                .getElementsAnnotatedWith(Seriable.class);  
        TypeElement classElement = null;// 声明类元素 
        List<VariableElement> fields = null;// 声明一个存放成员变量的列表 
        // 存放二者 类名:成员变量元素
        Map<String, List<VariableElement>> maps = new HashMap<String, List<VariableElement>>();  
        // 【2】遍历 ,找出标识注解的类或成员变量,封装到maps中;
        for (Element ele : elememts)  
        {  
            // 【2.1】判断该元素是否为类 
            if (ele.getKind() == ElementKind.CLASS)  
            {  
                classElement = (TypeElement) ele;  
                maps.put(classElement.getQualifiedName().toString(),  
                        fields = new ArrayList<VariableElement>()); //向maps中加入一个映射对 类名:变量元素的空容器 

            } else if (ele.getKind() == ElementKind.FIELD) // 【2.2】判断该元素是否为成员变量 
            {  
                VariableElement varELe = (VariableElement) ele;  
                // 获取该元素封装类型,即外层元素

                TypeElement enclosingElement = (TypeElement) varELe  
                        .getEnclosingElement();  //【2.2.1】获取最近的外层类,其实就是Class对象
                // 拿到key 
                String key = enclosingElement.getQualifiedName().toString();  
                fields = maps.get(key);  
                if (fields == null)  
                {  
                    maps.put(key, fields = new ArrayList<VariableElement>());  
                }  
                fields.add(varELe);  //【2.2.2】 向map的key=Class对象名所对应的Map中加入当前变量
            }  
        }  

        //【2.3】 遍历Maps,如果key所对应的容器容量为0,则证明是Seriable注解被加入到了类上,应该全类记录
        for (String key : maps.keySet())  
        {  
            if (maps.get(key).size() == 0)  
            {  
                TypeElement typeElement = elementUtils.getTypeElement(key);  //根据 类名获取到目标 类 元素
                List<? extends Element> allMembers = elementUtils  
                        .getAllMembers(typeElement);  //获取全部的 成员变量+成员函数 元素
                if (allMembers.size() > 0)  
                {  
                    maps.get(key).addAll(ElementFilter.fieldsIn(allMembers));  //过滤成员变量元素,并加入到对应的list中
                }  
            }  
        }  
        //【3】遍历maps为每个类创建json文件
        generateCodes(maps);  

        return true;  
    }  

    private void generateCodes(Map<String, List<VariableElement>> maps)  
    {  
        File dir = new File("f://apt_test");  
        if (!dir.exists())  
            dir.mkdirs();  
        // 遍历map 
        for (String key : maps.keySet())  
        {  

            // 创建文件 
            File file = new File(dir, key.replaceAll("\\.", "_") + ".txt");  
            try  
            {  
                /** * 编写json文件内容 */  
                FileWriter fw = new FileWriter(file);  
                fw.append("{").append("class:").append("\"" + key + "\"")  
                        .append(",\n ");  
                fw.append("fields:\n {\n");  
                List<VariableElement> fields = maps.get(key);  

                for (int i = 0; i < fields.size(); i++)  
                {  
                    VariableElement field = fields.get(i);  
                    fw.append(" ").append(field.getSimpleName()).append(":")  
                            .append("\"" + field.asType().toString() + "\"");  
                    if (i < fields.size() - 1)  
                    {  
                        fw.append(",");  
                        fw.append("\n");  
                    }  
                }  
                fw.append("\n }\n");  
                fw.append("}");  
                fw.flush();  
                fw.close();  

            } catch (IOException e)  
            {  
                e.printStackTrace();  
            }  

        }  
    }  

}

5.3 butterknife的 @BindView

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.tv_msg)
    TextView tvMsg;
    @BindView(R.id.tv_other)
    TextView tvOther;
}

先判断Element类型,如果是VariableElement则继续获取相关的包名(这里必须在app包名一致,不然获取不到android类)类对象信息,以及@BindView注解修饰的参数数据;
最后将所有需要的数据通过javapoet和Filer自动编译创建一个java文件
最终输出:

package com.wzgiceman.viewinjector;
import com.example.ViewInjector;
import java.lang.Object;
import java.lang.Override;

public class MainActivityViewInjector implements ViewInjector<MainActivity> {

  @Override
  public void inject(MainActivity host, Object object) {
     if(object instanceof android.app.Activity){
     host.tvMsg = (android.widget.TextView)(((android.app.Activity)object).findViewById(2131492945));
     }
    else{
     host.tvMsg = (android.widget.TextView)(((android.view.View)object).findViewById(2131492945));
    }
  }
}

处理器代码:

        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
        //一、收集信息
        for (Element element : elements) {
            /*检查类型*/
            if (!(element instanceof VariableElement)) {//BindView仅会作用在成员变量上,也可以使用ele.getKind() == ElementKind.FIELD判断
                return false;
            }
            VariableElement variableElement = (VariableElement) element;

            /*获取类信息*/
            TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
            /*类的绝对路径*/
            String qualifiedName = typeElement.getQualifiedName().toString();
            /*类名*/
            String clsName = typeElement.getSimpleName().toString();
            /*获取包名*/
            String packageName = processingEnv.getElementUtils().getPackageOf(typeElement).getQualifiedName().toString();

            /*获取注解*/
            BindView annotation = variableElement.getAnnotation(BindView.class);
            int id = annotation.value();

            /*参数名*/
            String name = variableElement.getSimpleName().toString();
            /*参数对象类*/
            String type = variableElement.asType().toString();

            ClassName InterfaceName = ClassName.bestGuess("com.example.annotation.api.ViewInjector");
            ClassName host = ClassName.bestGuess(qualifiedName);

            MethodSpec main = MethodSpec.methodBuilder("inject")
                    .addModifiers(Modifier.PUBLIC)
                    .returns(void.class)
                    .addAnnotation(Override.class)
                    .addParameter(host, "host")
                    .addParameter(Object.class, "object")
                    .addCode(""
                            + " if(object instanceof android.app.Activity){\n"
                            + " host." + name + " = (" + type + ")(((android.app.Activity)object).findViewById(" + id + "));\n"
                            + " }\n"
                            + "else{\n"
                            + " host." + name + " = (" + type + ")(((android.view.View)object).findViewById(" + id + "));\n"
                            + "}\n")
                    .build();

            TypeSpec helloWorld = TypeSpec.classBuilder(clsName + "ViewInjector")
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(main)
                    .addSuperinterface(ParameterizedTypeName.get(InterfaceName, host))
                    .build();

            try {
                // 生成 com.example.HelloWorld.java
                JavaFile javaFile = JavaFile.builder(packageName, helloWorld)
                        .addFileComment(" This codes are generated automatically. Do not modify!")
                        .build();
                // 生成文件
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
      }

需要注意的是,上述代码中,再一次遍历后就进行了IO操作,这也就导致了只初始化了tvMsg对象,tvOther并没有初始化,所以常规的做法应该是,统一扫描,统一处理,尤其注意重复数据的剔除

六、注册你的处理器

在自定义完注解处理器后,必须将它注册到javac中,才会在编译期间调用处理器对注解进行处理。

6.1 java ide
  • 你必须提供一个.jar文件。就像其他.jar文件一样,你打包你的注解处理器到此文件中

  • 在你的jar中,你需要打包一个特定的文件”javax.annotation.processing.Processor”META-INF/services路径下

  • 打包进MyProcessor.jar中的javax.annotation.processing.Processor的内容是,注解处理器的合法的全名列表,每一个元素换行分割

  • 把MyProcessor.jar放到你的builpath中,javac会自动检查和读取javax.annotation.processing.Processor中的内容,并且注册MyProcessor作为注解处理器

所以,你的.jar文件看起来就像下面这样:

MyProcessor.jar
    com
        example
            MyProcessor.class
    META-INF
        services
            javax.annotation.processing.Processor

其中javax.annotation.processing.Processor的内容为

com.example.MyProcessor  
com.foo.OtherProcessor  
net.blabla.SpecialProcessor
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值