• [转]Java中实现自定义的注解处理器


    Java中实现自定义的注解处理器(Annotation Processor)

    在之前的《简单实现ButterKnife的注解功能》中,使用了运行时的注解实现了通过编写注解绑定View与xml。由于运行时注解需要在Activity初始化中进行绑定操作,调用了大量反射相关代码,在界面复杂的情况下,使用这种方法就会严重影响Activity初始化效率。而ButterKnife使用了更高效的方式——Annotation Processor来完成这一工作。

    Annotation Processor即为注解的处理器。与运行时注解RetentionPolicy.RUNTIME不同,Annotation Processor处理RetentionPolicy.SOURCE类型的注解。在java代码编译阶段对标注RetentionPolicy.SOURCE类型的注解进行处理。这样在编译过程中添加代码,效率就非常高了。同样,Annotation Processor也可以实现IDE编写代码时的各种代码检验,例如当你在一个并未覆写任何父类方法的函数上添加了@Override注解,IDE会红线标识出你的函数提示错误。

    实现步骤

    使用Annotation Processor需要实现AbstraceProcessor这个抽象类,并配置工程引用这个Processor。 
    以下从Gradle编译工程及Eclipse中配置两方面介绍如何自定义并使用Annotation Processor。

    Gradle编译环境: 
    1.实现Annotation Processor 
    2.配置Processor工程的META_INF文件 
    3.在开发的代码中使用自定义注解 
    4.配置gradle编译脚本,引入processor工程 
    5.进行项目构建,查看processor输出

    Eclipse环境: 
    1.将Gradle环境编译出的processor.jar作为库引入到工程中 
    2.配置当前工程支持Annotation Processor,并使用自定义的processor.jar文件 
    3.开发代码使用自定义注解,查看IDE上提示信息

    *IDEA环境的配置与Eclipse类似,官网上已经有比较详细的描述了,可以查阅Jetbrain的官方文档。

    Gradle环境

    构建工程目录

    先来看一下processor工程的构建。 
    假设在HelloWorld工程中使用自定义的processor;独立于HelloWorld工程,我们独立开发了自定义的processor工程。项目结构如下:

    MyProcessorTest
    │          
    ├─MyProcessor
    │  │  
    │  └─src
    │      └─main
    │          └─java
    │             └─com
    │                 └─processor
    │                         MyProcessor.java
    │                         TestAnnotation.java
    │                          
    └─src
        └─main
            └─java
                └─com
                    └─hello
                            HelloWorld.java
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    主工程名为MyProcessorTest,在其中包含了processor工程MyProcessor

    实现自定义注解

    接下来实现一个自定义注解TestAnnotation

    package com.processor;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface TestAnnotation {
        int value();
        String what();
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    注意注解的Retention是RetentionPolicy.SOURCE类型。

    创建自定义Annotation Processor

    然后来实现自定义的Annotation Processor——MyProcessor

    package com.processor;
    
    import java.util.Set;
    
    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.RoundEnvironment;
    import javax.annotation.processing.SupportedAnnotationTypes;
    import javax.annotation.processing.SupportedSourceVersion;
    import javax.lang.model.SourceVersion;
    import javax.lang.model.element.TypeElement;
    
    @SupportedAnnotationTypes({"com.processor.TestAnnotation"})
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    public class MyProcessor extends AbstractProcessor {
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            System.out.println("Test log in MyProcessor.process");
            return false;
        }
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    其中几个要点: 
    1.自定义Annotation Processor需要继承AbstractProcessor,并覆写process方法。 
    2.需要声明此Processor所支持处理的注解类 
    @SupportedAnnotationTypes({"com.processor.TestAnnotation"}) 
    (类名需要已字符串形式填入包名+类名,否则不起作用) 
    3.需要声明注解作用的java版本,由于我的工程默认使用了JDK 7进行编译,因此需要填写 
    @SupportedSourceVersion(SourceVersion.RELEASE_7)

    目前自定义的Processor仅打印了一条log,如果成功的话,会在命令行编译时看到这条打印。 
    由于自定义Processor类最终是通过打包成jar,在编译过程中调用的。为了让java编译器识别出这个自定义的Processor,需要配置META-INF中的文件,将这个自定义的类名注册进去。 
    javax.annotation.processing.Processor文件:

    com.processor.MyProcessor
    • 1

    添加完META-INF后的MyProcessor工程结构如下:

    ├─MyProcessor
    │  │  
    │  └─src
    │      └─main
    │          ├─java
    │          │  └─com
    │          │      └─processor
    │          │              MyProcessor.java
    │          │              TestAnnotation.java
    │          │              
    │          └─resources
    │              └─META-INF
    │                  └─services
    │                          javax.annotation.processing.Processor
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这样自定义Processor的基本雏形就完成了。

    引用自定义注解

    接下来编写HelloWorld类,引入自定义注解:

    package com.hello;
    
    import com.processor.TestAnnotation;
    
    public class HelloWorld {
    
        @TestAnnotation(value = 5, what = "This is a test")
        public static String msg = "Hello world!";
    
        public static void main(String[] args) {
            System.out.println(msg);
        }
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在变量msg上使用了注解。

    配置Gradle编译环境

    下面来配置Gradle编译环境。 
    首先可以从一个Android工程里拷贝一份Gradle Wrapper到工程目录下(gradlew, gradlew.bat, 以及gradle目录),这时的工程结构(仅根目录下及gradle有关子目录):

    │  gradlew
    │  gradlew.bat
    │              
    ├─gradle
    │  └─wrapper
    │          gradle-wrapper.jar
    │          gradle-wrapper.properties
    │          
    ├─MyProcessor
    │  └─src
    │                      
    └─src
        └─main
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    此时还没有build.gradlesettings.gradle文件,因为我们有了gradle wrapper,因此可以通过它来自动生成这两个文件。执行命令

    gradlew.bat init
    • 1

    这样就生成默认的gradle配置文件了。接下来修改两个配置文件。 
    首先在settings.gradle中添加processor工程,以便在根目录下直接编译两个工程,以及后续的依赖配置。 
    settings.gradle

    rootProject.name = 'MyProcessorTest'
    include 'MyProcessor'
    • 1
    • 2

    然后在build.gradle中声明依赖,以便HelloWorld中自定义注解的处理以及processor的引用 
    build.gradle:

    apply plugin: 'java'
    
    dependencies {
        compile project('MyProcessor')
    }
    • 1
    • 2
    • 3
    • 4
    • 5

    上面的操作仅配置了根目录下的gradle配置,但MyProcessor中还没有配置。在MyProcessor的根目录下新建一个build.gradle即可: 
    build.gradle:

    apply plugin: 'java'
    • 1

    执行自定义Processor

    接下来就可以编译项目了,在根目录下执行

    gradlew.bat assemble
    • 1

    输出log:

    Executing command: ":assemble"
    :MyProcessor:compileJava UP-TO-DATE
    :MyProcessor:processResources UP-TO-DATE
    :MyProcessor:classes UP-TO-DATE
    :MyProcessor:jar UP-TO-DATE
    :compileJava
    Test log in MyProcessor.process
    Test log in MyProcessor.process
    :processResources UP-TO-DATE
    :classes
    :jar
    :assemble
    
    BUILD SUCCESSFUL
    
    Total time: 7.353 secs
    
    Completed Successfully
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    可以看到已经输出了Test log in MyProcessor.process,证明自定义的processor已经起作用了! 
    但是这里为何输出两次log? 
    在代码中添加几行调用:

        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            System.out.println("Test log in MyProcessor.process");
            System.out.println(roundEnv.toString());    //打印传入的roundEvn对象信息
            for (TypeElement element : annotations) {
                System.out.println(element.getSimpleName());    //遍历annotation,打印出注解类型
            }
            return false;
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    再次运行log输出:

    Compiling with JDK Java compiler API.
    Test log in MyProcessor.process
    [errorRaised=false, rootElements=[com.hello.HelloWorld], processingOver=false]
    TestAnnotation
    Test log in MyProcessor.process
    [errorRaised=false, rootElements=[], processingOver=true]
    :compileJava (Thread[main,5,main]) completed. Took 0.249 secs.
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    可以看出仅在第一次process处理了TestAnnotation注解,第二次并没有遍历到;并且processingOver状态不同。这里的具体流程还没搞懂,先略过……

    添加注解处理及信息提示

    最后一步,就要为注解程序添加真正的处理功能了。直接放代码:

    @SupportedAnnotationTypes({"com.processor.TestAnnotation"})
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    public class MyProcessor extends AbstractProcessor {
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            System.out.println("Test log in MyProcessor.process");
            System.out.println(roundEnv.toString());
    
            for (TypeElement typeElement : annotations) {    // 遍历annotations获取annotation类型
                for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) {    // 使用roundEnv.getElementsAnnotatedWith获取所有被某一类型注解标注的元素,依次遍历
                    // 在元素上调用接口获取注解值
                    int annoValue = element.getAnnotation(TestAnnotation.class).value();
                    String annoWhat = element.getAnnotation(TestAnnotation.class).what();
    
                    System.out.println("value = " + annoValue);
                    System.out.println("what = " + annoWhat);
    
                    // 向当前环境输出warning信息
                    processingEnv.getMessager().printMessage(Kind.WARNING, "value = " + annoValue + ", what = " + annoWhat, element);
                }
            }
            return false;
        }
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    这次仅运行gradlew.bat compileJava

    Executing command: ":compileJava"
    :MyProcessor:compileJava
    :MyProcessor:processResources UP-TO-DATE
    :MyProcessor:classes
    :MyProcessor:jar
    :compileJava
    Test log in MyProcessor.process
    [errorRaised=false, rootElements=[com.hello.HelloWorld], processingOver=false]
    D:	estMyProcessorTestsrcmainjavacomhelloHelloWorld.java:8: 警告: value = 5, what = This is a test
        public static String msg = "Hello world!";
                             ^
    1 个警告
    value = 5
    what = This is a test
    Test log in MyProcessor.process
    [errorRaised=false, rootElements=[], processingOver=true]
    
    BUILD SUCCESSFUL
    
    Total time: 9.048 secs
    
    Completed Successfully
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    自定义processor已经起作用了。最后的

    processingEnv.getMessager().printMessage(Kind.WARNING, "value = " + annoValue + ", what = " + annoWhat, element);
    • 1

    在命令行环境中抛出了warning。 
    实际processingEnv不仅可以作用于命令行,对IDE同一样生效。

    Eclipse环境配置

    下面再看看目前编译好的Processor如何在Eclipse环境中生效。

    在网上搜索到Eclipse官方文档: 
    Introduction to Annotation Processing in Eclipse 
    介绍了如何配置Eclipse工程来支持注解处理器。

    步骤如下: 
    1.在当前工程中开启Annotation Processing。 
    通过工程属性,开启Java Compiler->Annotation Processing中的选项; 
    开启Annotation 
    开启Annotation


    继续设置Java Compiler->Annotation Processing -> Factory Path,导入自定义Processer的jar文件(导入刚刚编译出的MyProcessor.jar)。 
    使用自定义的Processor 
    使用自定义的Processor


    2.在代码中引用注解,显示自定义Processor中的提示信息: 
    显示Processor中的警告 
    显示Processor中的警告


    这时Eclipse中的工程结构: 
    工程结构

    Eclipse的官方文档中给了一个验证用的APTDemo.jar。实际反编译后可以看到jar中使用了Java 5的注解处理器API实现的功能(包名还是sun的)。 
    而在上面的样例代码中,使用的是Java 6中的API实现的。

    另外在Eclipse的官方文档: 
    Eclipse官方文档 
    JDT Plug-in Developer Guide > Programmer’s Guide > JDT Annotation Processing 
    一节中,说道:

    Eclipse 3.2 provided support for annotation processors using the Java 5 Mirror APIs, and Eclipse 3.3 added support for processors using the Java 6 annotation processing APIs.

    也就是以上的方法对Eclipse 3.3以上版本有效。 
    同时还有:

    Eclipse does not support executing Java 6 processors while typing in the editor; you must save and build in order for Java 6 processors to report errors or generate files.

    然而至少通过Mars版本(4.5.2)的Eclipse,是可以在编辑器中看到warning信息的,有可能是文档这里仍没有更新……

  • 相关阅读:
    关于剪切板
    编译器选项
    【转】预编译头没有使用的问题
    【转】fatal error C1900: “P1”(第“20081201”版)和“P2”(第“20080116”版)之间 Il 不匹配
    pImpl
    HIVE 2.3.4 本地安装与部署 (Ubuntu)
    计算机网络 自顶向下 复习提要 数据链路层
    计算机网络 自顶向下 复习提要 网络层2
    计算机网络 自顶向下 复习提要 网络层
    计算机网络 自顶向下 复习提要 传输层(TCP)
  • 原文地址:https://www.cnblogs.com/didiaoxiong/p/9117972.html
Copyright © 2020-2023  润新知