概述
Java中的注解常用语描述或者标记的作用,通俗的讲,就是一组语法元数据,可以用于从java代码中抽取文档、跟踪代码中的依赖性或者在编译时做检查.
可以添加到Java源代码.类、方法、变量、参数、包都可以被注解.
java8后新增了一种类型注解,可以用在代码的任何位置.可用来将信息元数据与程序元素进行关联,参考阅读Java 8新特性探究(四)类型注解 复杂还是便捷
注解作用
注解大体有如下三类作用:
- 标记(如
@Override
,@Deprecated
),java标准注解就是这个作用 - 编译时动态处理,动态生成代码,如
butterknife
,Dagger2
等 - 运行时动态处理,得到注解信息,如
Retrofit
等
注解分类
标准 Annotation
Java
语法本身自带提供了三个 Annotation
,主要起到提示作用,分别是:
- Override(重写函数), 被
@Override
标注的方法如果没有覆盖父类的方法,编译时报错。 - Deprecated(不鼓励使用), 无论是继承、覆盖或直接使用此方法,编译器都会给出警告。
- SuppressWarnings(忽略某项 Warning),告诉编译器,对被标注的这句代码不要给出特定的警告.
常见的压制警告参数有:deprecation,unchecked,fallthrough,path,serial,serialVersionUID,finally,all.
元 Annotation
元注解通常被称为注解的注解,因为定义注解的时候需要用到元注解.java提供了四类元注解,分别是:
@Retention(保留时间):
- SOURCE(源码时),在源文件中有效,SOURCE 大都为标记注解,如Java标准2. Annotation
- CLASS(编译时),在class文件中有效
- RUNTIME(运行时),在运行时有效
@Target(可修饰的程序元素):
- TYPE(描述类、接口(包括注解类型) 或enum声明),
- METHOD(描述方法),
- CONSTRUCTOR(描述构造器),
- FIELD(描述域),
- PARAMETER(描述参数),
- LOCAL_VARIABLE(描述局部变量),
- PACKAGE(描述包)
- 未标注则表示可修饰所有的类型
@Inherited(是否可以被继承):
默认为false
此注解说明它注解的类将自动地把这个注解传递到所有子类中而不用在子类中声明。
@Documented(会保存到 Javadoc 文档中):
默认为false
自定义Annotation
自定义注解可以使用上面的元 Annotation
,根据作用域分为源码时、编译时、运行时 Annotation.
Annotation自定义
定义注意事项
一. 通过 @interface
定义,如
public @interface 注解名 {定义体}
二. 注解方法没有方法体,没有参数没有修饰符(允许 public
& abstract
,默认为public
),不允许抛异常
三. 返回值只能是基本类型,String
, Class
, annotation
, enumeration
或者是他们的一维数组
四. 只有一个默认属性,可直接用 value()
函数
五. 可以加 default
表示默认值.
六. 注解参数的支持类型有
- 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
- String类型
- Class类型
- enum类型
- Annotation类型
- 以上所有类型的数组
Annotation 解析
运行时注解
运行时注解即@Retention
为 RUNTIME
的 Annotation
,一般需要用到反射
如,其 @Target
为 METHOD
的运行时注解的解析
method.getAnnotation(AnnotationName.class);//得到某个 Annotation 的信息
method.getAnnotations();//得到该 Target 所有 Annotation
method.isAnnotationPresent(AnnotationName.class);//该 Target 是否被某个 Annotation 修饰
编译时注解
而编译时注解即@Retention
为 CLASS
的 Annotation
,有编译器在编译时自动完成,编译时注解需要用到apt
技术,定义一个编译时注解的过程如下
- 自定义类继承
AbstractProcessor
,并重写其中的process
函数 - 在
process
函数中收集注解信息,并生成相关类 process
函数返回值表示这组annotations
是否被这个Processor
接受,如果接受后续的processor
不会再对这个Annotations
进行处理
@AutoService(Processor.class)//需要添加compile 'com.google.auto.service:auto-service:1.0-rc2'依赖
@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) { }
}
java8 中的类型注解
在Java8中的Annotations是可重复的。
@interface Hints {
Hint[] value();
}
@Repeatable(Hints.class)
@interface Hint {
String value();
}
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}
Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length); // 2
Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);
System.out.println(hints2.length); // 2
相关概念
- 注解处理器(
Annotation Processor
)是javac
的一个工具 - 虚处理器写好后需要注册到
JVM
,编译器才能在编译时自动查找所有继承自AbstractProcessor
的类,然后调用他们的process
方法去处理 - 处理器上添加
@AutoService(Processor.class)
即可自动注册到JVM中.AutoService
注解处理器是Google开发的,会自动生成META-INF/services/javax.annotation.processing.Processor
文件 - 注解处理器中提供了一些好用的工具包括,如
Elements
,Types
,Filer
,Messager
,他们都可以通过init
方法中的参数获得 Elements
分类包括如下
package com.example; // PackageElement
public class Foo { // TypeElement
private int a; // VariableElement
private Foo other; // VariableElement
public Foo () {} // ExecuteableElement
public void setA ( // ExecuteableElement
int newA // TypeElement
) {}
}
我们可以使用EmentKind
或者TypeKind
来判断Element
的类型,如
// 检查被注解为@Factory的元素是否是一个类
if (annotatedElement.getKind() != ElementKind.CLASS) {
//...
}
最佳实践
基于编译时注解项目,比较著名的有想 ButterKnife,EventBus3,而运行时项目 有 Retrofit等,相关阅读:Android 打造编译时注解解析框架 这只是一个开始
实践Demo:
AnnotationDemo
参考与扩展阅读:
Java注解处理器:讲解非常详细