首页 技术 正文
技术 2022年11月16日
0 收藏 497 点赞 3,080 浏览 8196 个字
Java中实现自定义的注解处理器(Annotation Processor)

置顶2016年07月25日 19:42:49

阅读数:9877

在之前的《简单实现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
:assembleBUILD SUCCESSFULTotal time: 7.353 secsCompleted 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:\test\MyProcessorTest\src\main\java\com\hello\HelloWorld.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 SUCCESSFULTotal time: 9.048 secsCompleted 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中的选项; 
[转]Java中实现自定义的注解处理器 
开启Annotation


继续设置Java Compiler->Annotation Processing -> Factory Path,导入自定义Processer的jar文件(导入刚刚编译出的MyProcessor.jar)。 
[转]Java中实现自定义的注解处理器 
使用自定义的Processor


2.在代码中引用注解,显示自定义Processor中的提示信息: 
[转]Java中实现自定义的注解处理器 
显示Processor中的警告


这时Eclipse中的工程结构: 
[转]Java中实现自定义的注解处理器

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信息的,有可能是文档这里仍没有更新……

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,082
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,557
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,406
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,179
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,815
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,898