Java Annotation 及几个常用开源项目注解原理简析

文简单介绍下 Annotation 示例、概念及作用、分类、自定义、解析,并对几个 Android 开源库 Annotation 原理进行简析
PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotation.key

一、Annotation 示例

Override Annotation

Java


1

2


@Override

public void onCreate(Bundle savedInstanceState);

Retrofit Annotation

Java


1

2


@GET("/users/{username}")

User getUser(@Path("username") String username);

Butter Knife Annotation

Java


1


@InjectView(R.id.user) EditText username;

ActiveAndroid Annotation

Java


1


@Column(name = “Name") public String name;

Retrofit 为符合 RESTful 规范的网络请求框架
Butter Knife 为 View 及事件等依赖注入框架
Active Android 为 ORM 框架
更多见:Android 开源项目汇总

二、Annotation 概念及作用

1 概念

An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

能够添加到 Java 源代码的语法元数据。类、方法、变量、参数、包都可以被注解,可用来将信息元数据与程序元素进行关联。Annotation 中文常译为“注解”

2 作用

a. 标记,用于告诉编译器一些信息
b. 编译时动态处理,如动态生成代码
c. 运行时动态处理,如得到注解信息
这里的三个作用实际对应着后面自定义 Annotation 时说的 @Retention 三种值分别表示的 Annotation
看看下面一段代码的运行结果是多少?

Java


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

27


public class Person {

private int id;

private String name;

public Person(int id, String name) {

this.id = id;

this.name = name;

}

public boolean equals(Person person) {

return person.id == id;

}

public int hashCode() {

return id;

}

public static void main(String[] args) {

Set<Person> set = new HashSet<Person>();

for (int i = 0; i < 10; i++) {

set.add(new Person(i, "Jim"));

}

System.out.println(set.size());

}

}

答案见本文最后

三、Annotation 分类

1 标准 Annotation

包括 Override, Deprecated, SuppressWarnings,标准 Annotation 是指 Java 自带的几个 Annotation,上面三个分别表示重写函数,不鼓励使用(有更好方式、使用有风险或已不在维护),忽略某项 Warning

2 元 Annotation

@Retention, @Target, @Inherited, @Documented,元 Annotation 是指用来定义 Annotation 的 Annotation,在后面 Annotation 自定义部分会详细介绍含义

3 自定义 Annotation

自定义 Annotation 表示自己根据需要定义的 Annotation,定义时需要用到上面的元 Annotation
这里只是一种分类而已,也可以根据作用域分为源码时、编译时、运行时 Annotation,后面在自定义 Annotation 时会具体介绍

四、Annotation 自定义

1 调用

Java


1

2

3

4

5

6

7

8

9

10


public class App {

@MethodInfo(

author = “trinea.cn+android@gmail.com”,

date = "2014/02/14",

version = 2)

public String getAppName() {

return "trinea";

}

}

这里是调用自定义 Annotation——MethodInfo 的示例,MethodInfo Annotation 作用为给方法添加相关信息,包括 author、date、version

2 定义

Java


1

2

3

4

5

6

7

8

9

10

11

12


@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

@Inherited

public @interface MethodInfo {

String author() default "trinea@gmail.com";

String date();

int version() default 1;

}

这里是 MethodInfo 的实现部分
(1). 通过 @interface 定义,注解名即为自定义注解名
(2). 注解配置参数名为注解类的方法名,且:
a. 所有方法没有方法体,没有参数没有修饰符,实际只允许 public & abstract 修饰符,默认为 public ,不允许抛异常
b. 方法返回值只能是基本类型,String, Class, annotation, enumeration 或者是他们的一维数组
c. 若只有一个默认属性,可直接用 value() 函数。一个属性都没有表示该 Annotation 为 Mark Annotation
(3). 可以加 default 表示默认值

3 元 Annotation

@Documented 是否会保存到 Javadoc 文档中
@Retention 保留时间,可选值 SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为 CLASS,值为 SOURCE 大都为 Mark Annotation,这类 Annotation 大都用来校验,比如 Override, Deprecated, SuppressWarnings
@Target 可以用来修饰哪些程序元素,如 TYPE, METHOD, CONSTRUCTOR, FIELD, PARAMETER 等,未标注则表示可修饰所有
@Inherited 是否可以被继承,默认为 false

五、Annotation 解析

1 运行时 Annotation 解析

(1) 运行时 Annotation 指 @Retention 为 RUNTIME 的 Annotation,可手动调用下面常用 API 解析

Java


1

2

3


method.getAnnotation(AnnotationName.class);

method.getAnnotations();

method.isAnnotationPresent(AnnotationName.class);

其他 @Target 如 Field,Class 方法类似
getAnnotation(AnnotationName.class) 表示得到该 Target 某个 Annotation 的信息,因为一个 Target 可以被多个 Annotation 修饰
getAnnotations() 则表示得到该 Target 所有 Annotation
isAnnotationPresent(AnnotationName.class) 表示该 Target 是否被某个 Annotation 修饰
(2) 解析示例如下:

Java


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17


public static void main(String[] args) {

try {

Class cls = Class.forName("cn.trinea.java.test.annotation.App");

for (Method method : cls.getMethods()) {

MethodInfo methodInfo = method.getAnnotation(

MethodInfo.class);

if (methodInfo != null) {

System.out.println("method name:" + method.getName());

System.out.println("method author:" + methodInfo.author());

System.out.println("method version:" + methodInfo.version());

System.out.println("method date:" + methodInfo.date());

}

}

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

以之前自定义的 MethodInfo 为例,利用 Target(这里是 Method)getAnnotation 函数得到 Annotation 信息,然后就可以调用 Annotation 的方法得到响应属性值

2 编译时 Annotation 解析

(1) 编译时 Annotation 指 @Retention 为 CLASS 的 Annotation,甴 apt(Annotation Processing Tool) 解析自动解析。需要做的
a. 自定义类集成自 AbstractProcessor
b. 重写其中的 process 函数
这块很多同学不理解,实际是 apt(Annotation Processing Tool) 在编译时自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法去处理
(2) 假设之前自定义的 MethodInfo 的 @Retention 为 CLASS,解析示例如下:

Java


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15


@SupportedAnnotationTypes({ "cn.trinea.java.test.annotation.MethodInfo" })

public class MethodInfoProcessor extends AbstractProcessor {

@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {

HashMap<String, String> map = new HashMap<String, String>();

for (TypeElement te : annotations) {

for (Element element : env.getElementsAnnotatedWith(te)) {

MethodInfo methodInfo = element.getAnnotation(MethodInfo.class);

map.put(element.getEnclosingElement().toString(), methodInfo.author());

}

}

return false;

}

}

SupportedAnnotationTypes 表示这个 Processor 要处理的 Annotation 名字。
process 函数中参数 annotations 表示待处理的 Annotations,参数 env 表示当前或是之前的运行环境
process 函数返回值表示这组 annotations 是否被这个 Processor 接受,如果接受后续子的 rocessor 不会再对这个 Annotations 进行处理

六、几个 Android 开源库 Annotation 原理简析

1 Annotation — Retrofit

(1) 调用

Java


1

2


@GET("/users/{username}")

User getUser(@Path("username") String username);

(2) 定义

Java


1

2

3

4

5

6

7


@Documented

@Target(METHOD)

@Retention(RUNTIME)

@RestMethod("GET")

public @interface GET {

String value();

}

从定义可看出 Retrofit 的 Get Annotation 是运行时 Annotation,并且只能用于修饰 Method
(3) 原理

Java


1

2

3

4

5

6

7

8

9

10

11

12

13

14


private void parseMethodAnnotations() {

for (Annotation methodAnnotation : method.getAnnotations()) {

Class<? extends Annotation> annotationType = methodAnnotation.annotationType();

RestMethod methodInfo = null;

for (Annotation innerAnnotation : annotationType.getAnnotations()) {

if (RestMethod.class == innerAnnotation.annotationType()) {

methodInfo = (RestMethod) innerAnnotation;

break;

}

}

……

}

}

RestMethodInfo.java 的 parseMethodAnnotations 方法如上,会检查每个方法的每个 Annotation, 看是否被 RestMethod 这个 Annotation 修饰的 Annotation 修饰,这个有点绕,就是是否被 GET、DELETE、POST、PUT、HEAD、PATCH 这些 Annotation 修饰,然后得到 Annotation 信息,在对接口进行动态代理时会掉用到这些 Annotation 信息从而完成调用。 因为 Retrofit 原理设计到动态代理,这里只介绍 Annotation,具体原理分析请等待 Android 优秀开源项目实现原理解析 项目的完成

2 Annotation — Butter Knife

(1) 调用

Java


1

2


@InjectView(R.id.user)

EditText username;

(2) 定义

Java


1

2

3

4

5


@Retention(CLASS)

@Target(FIELD)

public @interface InjectView {

int value();

}

可看出 Butter Knife 的 InjectView Annotation 是编译时 Annotation,并且只能用于修饰属性
(3) 原理

Java


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21


@Override

public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {

Map<TypeElement, ViewInjector> targetClassMap = findAndParseTargets(env);

for (Map.Entry<TypeElement, ViewInjector> entry : targetClassMap.entrySet()) {

TypeElement typeElement = entry.getKey();

ViewInjector viewInjector = entry.getValue();

try {

JavaFileObject jfo = filer.createSourceFile(viewInjector.getFqcn(), typeElement);

Writer writer = jfo.openWriter();

writer.write(viewInjector.brewJava());

writer.flush();

writer.close();

} catch (IOException e) {

error(typeElement, "Unable to write injector for type %s: %s", typeElement, e.getMessage());

}

}

return true;

}

ButterKnifeProcessor.java 的 process 方法如上,编译时,在此方法中过滤 InjectView 这个 Annotation 到 targetClassMap 后,会根据 targetClassMap 中元素生成不同的 class 文件到最终的 APK 中,然后在运行时调用 ButterKnife.inject(x) 函数时会到之前编译时生成的类中去找。 这里只介绍 Annotation,具体原理分析请等待 Android 优秀开源项目实现原理解析 项目的完成

3 Annotation — ActiveAndroid

(1) 调用

Java


1

2


@Column(name = “Name")

public String name;

(2) 定义

Java


1

2

3

4

5


@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Column {

……

}

可看出 ActiveAndroid 的 Column Annotation 是运行时 Annotation,并且只能用于修饰属性
(3) 原理

Java


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17


Field idField = getIdField(type);

mColumnNames.put(idField, mIdName);

List<Field> fields = new LinkedList<Field>(ReflectionUtils.getDeclaredColumnFields(type));

Collections.reverse(fields);

for (Field field : fields) {

if (field.isAnnotationPresent(Column.class)) {

final Column columnAnnotation = field.getAnnotation(Column.class);

String columnName = columnAnnotation.name();

if (TextUtils.isEmpty(columnName)) {

columnName = field.getName();

}

mColumnNames.put(field, columnName);

}

}

TableInfo.java 的构造函数如上,运行时,得到所有行信息并存储起来用来构件表信息。

这里原理都只介绍 Annotation,具体原理分析请等待 Android 优秀开源项目实现原理解析 项目的完成

最后留下一个小问题:如何判断一个 Annotation 是运行时还是编译时生效以及如何快速找到它的解析代码

前面的示例代码运行结果应该是 10 而不是 1,这个示例代码程序实际想说明的是标记型注解 Override 的作用,为 equals 方法加上 Override 注解就知道 equals 方法的重载是错误的,参数不对

时间: 2024-12-31 13:31:05

Java Annotation 及几个常用开源项目注解原理简析的相关文章

值的关注的Java开源项目(原创)

项目|原创 值的关注的Java开源项目   名称 资料 概况 OFBiz http://ofbizchina.com:8080/ http://www.ofbiz.org/ https://ofbiz.dev.java.net/ OFBiz是一个非常著名的开源项目,提供了创建基于最新J2EE/XML规范和技术标准,构建大中型企业级.跨平台.跨数据库.跨应用服务器的多层.分布式电子商务类WEB应用系统的框架.     OFBiz最主要的特点是OFBiz提供了一整套的开发基于Java的web应用程序

GitHub上史上最全的Android开源项目分类汇总

今天在看博客的时候,无意中发现了 @Trinea在GitHub上的一个项目 Android开源项目分类汇总,由于类容太多了,我没有一个个完整地看完,但是里面介绍的开源项目都非常有参考价值,包括很炫的界面特效设计.个性化控件.工具库.优秀的Android开源项目.开发测试工具.优秀个人和团体等.可以这样说,每一位Andorid开发人员都能从中找到一个或多个适用自己项目的解决方案,消化吸收并加以利用,可以为自己的APP增色不少.文章最后还列出了部分国外著名Android开发者的信息,包括GitHub

java Annotation 注解

首先什么是注解? 最常见的是,在我们使用Eclipse等工具编写java代码的时候,有时候会出现一些比如@Deprecated,@Override,@SuppressWarnings等东东.这个就是常见的几种注解. 在开发Java程序,尤其是Java EE应用的时候,总是免不了与各种配置文件打交道.以Java EE中典型的S(pring)S(truts)H(ibernate)架构来说,Spring.Struts和Hibernate这三个框架都有自己的XML格式的配置文件.这些配置文件需要与Jav

Java开源项目Hibernate快速入门

快速入门|项目   Java开源项目Hibernate快速入门其实Hibernate本身是个独立的框架,它不需要任何web server或application server的支持.然而,大多数的Hibernate入门介绍都加入了很多非Hibernate的东西,比如: Tomcat, Eclipse, Log4J,Struts, XDoclet, 甚至JBoss.这容易让人产生Hibernate复杂难懂的误解,特别是打击了初学者的积极性. 在这篇文章将不涉及Eclipse, log4j, Str

GitHub 上最火的开源项目 —— Java 篇

很多开发者在看到自己感兴趣的项目时会使用 Star 功能,可以说 Star 的数量在一定程度上代表了开源项目的热门程度,本文整理了 Java 语言中 star 数最多的十五款开源项目,这些项目在 GitHub 上的 star 数均超过 15000,排名如下: Rx 的 Java 实现 RxJava star:26782;fork:4718 .Net响应式编程框架 Rx 的Java版本开源实现,有Groovy, Clojure, Scala and JRuby版本的实现. 分布式搜索引擎 Elas

JeeSite(1):推荐一个java开源项目 JeeSite

本文的原文连接是: http://blog.csdn.net/freewebsys/article/details/50778119 未经博主允许不得转载. 博主地址是:http://blog.csdn.net/freewebsys 1,关于JeeSite http://jeesite.com/ https://github.com/thinkgem/jeesite JeeSite是基于多个优秀的开源项目,高度整合封装而成的高效,高性能,强安全性的开源Java EE快速开发平台. JeeSite

java开源项目研究 3.许可协议介绍

项目     如果你只是想学习使用一下开源项目,那么你可以跳过本节(了解一下更好^_^),但是如果你想在自己的产品(不管是开源的还是商业的)中集成开源项目,那么你必须了解许可协议.        开源项目的License可以分为三大类:     OSI-Approved Open Source:被开放源码组织(www.opensource.org)所批准的开放源码授权协议.像大家经常看到的Apache,GPL,LGPL,MIT Licence,都属于OSI-Approved的授权协议,OSI 的

java开源项目研究 2. 开源的历史

项目     提到开源,就不得不提到Richard Stallman--开源软件的始创者和精神领袖(不是偶像崇拜^_^).他是GNU(GNU's Not UNIX)工程的发起人,FSF(Free Software Foundation)的缔造者,同时还是大名鼎鼎的GNU Emacs(一个编辑工具),GCC(c/c++编译器,不用我说了吧,呵呵),GDB(调试工具)和GNU Make的作者.     Stallman的生平是和开源历史紧密相关的:     1984年,Stallman辞去了MIT

java开源项目研究 1. 引子

项目       一夜之间,开源项目仿佛无处不在,几乎覆盖了包括操作系统.网络通讯.桌面环境.教育.办公.安全.文字处理.数据库.中间件.应用娱乐在内的所有软件类型.在最大的开源项目发布平台www.sourceforge.net上,已经有近十万件开源项目,并且每天都有更多新的开源项目加入.而著名的www.apache.org则为大家奉献了Apache.Tomcat.Struts .Axis等.       开源运动是程序员对智慧成果共享.自由的追求,每天都有无数程序员为开源世界添砖加瓦,还有更多