[转]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.gradle
和settings.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:\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 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
继续设置Java Compiler->Annotation Processing -> Factory Path,导入自定义Processer的jar文件(导入刚刚编译出的MyProcessor.jar)。
使用自定义的Processor
2.在代码中引用注解,显示自定义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信息的,有可能是文档这里仍没有更新……
[转]Java中实现自定义的注解处理器的更多相关文章
- Mybatis中使用自定义的类型处理器处理枚举enum类型
知识点:在使用Mybatis的框架中,使用自定义的类型处理器处理枚举enum类型 应用:利用枚举类,处理字段有限,可以用状态码,代替的字段,本实例,给员工状态字段设置了一个枚举类 状态码,直接赋值给对 ...
- 自定义jsr-269注解处理器 Error:服务配置文件不正确,或构造处理程序对象javax.annotation.processing.Processor: Provider not found
出现的原因 自定义处理器还没有被编译就被调用,所以报 not found在根据配置寻找自定义的注解处理器时,自定义处理器还未被编译12解决方式 maven项目可以配置编译插件,在编译项目之前先编译处理 ...
- Springboot中使用自定义参数注解获取 token 中用户数据
使用自定义参数注解获取 token 中User数据 使用背景 在springboot项目开发中需要从token中获取用户信息时通常的方式要经历几个步骤 拦截器中截获token TokenUtil工具类 ...
- Java中的反射和注解
前言 在Java中,反射机制和注解机制一直是一个很重要的概念,那么他们其中的原理是怎么样呢,我们不仅仅需要会使用,更要知其然而之所以然. 目录 反射机制 反射如何使用 注解定义 注解机制原理 注解如何 ...
- Java中的自定义数组队列
在Java中,作为所有数据结构中存储和获取速度最快的一种,数组凭借其这种简单易用的优势在各个方面都能大显神威.但是数组也有自身的局限性.数组的长度必须是固定的一旦定义之后就无法动态的更改,这就会造成这 ...
- Java中的自定义注解
## 元注解 要声明一个注解, 我们需要元注解, 元注解是指注解的注解,包括@Retention, @Target, @Document, @Inherited. @Retention 注解的保留位置 ...
- java中的自定义注解的使用
https://www.cnblogs.com/acm-bingzi/p/javaAnnotation.html
- 关于JAVA中源码级注解的编写及使用
一.注解简介: 1.1.什么是"注解": 在我们编写代码时,一定看到过这样的代码: class Student { private String name; @Override ...
- java 中的 自定义viewUtils框架
在前面学习的基础上,我们自己编写一个ViewUtils注解框架类,不清楚的原理看前面的源代码 package im.weiyuan.com.viewutils; import android.supp ...
随机推荐
- Delphi通过POST传递参数给PHP
Delphi代码 ******************************************************************************************* ...
- wukong引擎源码分析之索引——part 1 倒排列表本质是有序数组存储
searcher.IndexDocument(0, types.DocumentIndexData{Content: "此次百度收购将成中国互联网最大并购"}) engine.go ...
- 深入理解WeakHashmap
转自:http://mikewang.blog.51cto.com/3826268/880775 (一) 查看API文档,WeakHashmap要点如下: 1. 以弱键 实现的基于哈希表的 Map.在 ...
- CodeForces-668D:Remainders Game (中国剩余定理||理解)
Today Pari and Arya are playing a game called Remainders. Pari chooses two positive integer x and k, ...
- 【Codeforces 632D】 Longest Subsequence
[题目链接] 点击打开链接 [算法] 设取的所有数都是k的约数,则这些数的lcm必然不大于k. 对于[1, m]中的每个数,统计a中有多少个数是它的约数即可. [代码] #include<bit ...
- 【USACO】 Balanced Lineup
[题目链接] 点击打开链接 [算法] 这是一道经典的最值查询(RMQ)问题. 我们首先想到线段树.但有没有更快的方法呢?对于这类问题,我们可以用ST表(稀疏表)算法求解. 稀疏表算法.其实也是一种动态 ...
- 【转】Selenium模拟JQuery滑动解锁
滑动解锁一直做UI自动化的难点之一,我补一篇滑动解锁的例子,希望能给初做Web UI自动化测试的同学一些思路. 首先先看个例子. https://www.helloweba.com/demo/2017 ...
- RobotFramework:App滑动屏幕
转自:http://blog.csdn.net/jgw2008/article/details/77993399 在使用Robot Framework测试Android机器过程中, 经常要用到滚屏操作 ...
- hdoj3790 【最短路】
这一题啊,其实还是很简单的~(A掉了就很简单啊~) 思路: 松弛,然后在里面维护一个小最短路~: A掉这一题,感觉松弛的理解又上了一个台阶,以及spfa的原理,最短路用到的原理就是松弛,先把图构造到最 ...
- python __builtins__ filter类 (24)
24.'filter', 用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表.该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判,然后返回 True ...