关于注解,你需要知道的一些知识:
Annotation注解(一)- 基础
Annotation注解(二)- 进阶 - 自定义ButterKnife
这篇博客,主要讲解关于注解的一些基本知识,包括注解的概念、分类、作用,常见注解的定义及其解析方式等。
尊重原创,转载请注明出处 https://segmentfault.com/a/11...
本文出自 强哥大天才的博客
Annotation的概念
1. 概念
关于Annotation注解的概念,我们可以看下官方的解释:
Annotations, a form of
metadata
;provide data
about a program that is not part of the program itself.
Annotationshave no direct effect
on the operation of the code they annotate
Annotation注解是Java中的一种元数据,它可以往源代码中添加额外的数据,并且对注解的代码无直接影响;包名、类、成员方法、成员属性、参数都可以被注解。
2. 作用
注解可以让我们的代码更加简洁,并且增加代码的复用性,避免冗余代码。
如果细分起来,注解主要有3个作用:
-
编译时标记:在编译时,通过标记代码,来
提示信息
、检查错误 -
编译时处理:在编译时,通过构建代码,来动态
生成一些额外的代码
、或者Java/XML等文件 -
运行时处理:在运行时,我们可以动态的
获取注解信息
3. 分类
1. 标准Annotation
标准Annotation是指Java自带的Annotation。
eg:@Override[重写]、@Deprecated[不鼓励使用]、@SuppressWarnings[忽略警告]等。
2. 元Annotation
元Annotation是指注解Annotation的Annotation。
eg:@Retention, @Target, @Inherited, @Documented等。
3. 自定义Annotation
我们可以利用元Annotation来自定义一些自己的Annotation。
元Annotation
元Annotation是指注解Annotation的Annotation。
-
@Documented
:Annotation是否会保存到JavaDoc文档中 -
@Inherited
:Annotation是否可以被继承,默认是false -
@Target(ElementType[] types)
:Annotation可以用来注解哪些元素
ElementType:元素类型,比如类、方法、变量、参数等
-
@Retention(RetentionPolicy ploicy)
:Annotation的保留策略
RetentionPolicy.SOURCE:Annotation仅保留在java源码中
- 大都为Mark Annotation,做一个警示、校验的作用
RetentionPolicy.CLASS:Annotation保留在java源码、class文件中
- 默认的保留策略,一般配合Processor注解处理器,在构建时动态生成代码
RetentionPolicy.RUNTIME:Annotation保留在java源码、class文件、运行时
- 与CLASS的区别是,这类Annotation在运行时会被加载到JVM中,因此我们可以在程序运行的过程中动态获取到Annotation的相关信息
自定义Annotation
我们先来看一下一个最为基础的Annotation示例代码(这是一个自动生成类头信息的一个注解)
调用
@ClassInfo (
author = "xzqbetter@163.com",
date = "11/17/2017",
currentRevision = 6,
reviewers = {"Alice", "Bob", "Cindy"} // Note array notation
)
public class Test1 {
// class code goes here
}
定义
public @interface ClassInfo {
String author();
String date();
int currentRevision() default 1;
String[] reviewers(); // Note use of array
}
解释
1.注解的定义:可以看到,我们利用@interface,定义了一个注解类
,之后就可以在代码中使用这个注解
- @interface是interface的一种变异形式
2.参数的定义:在注解类内部,我们声明了很多抽象方法,来定义注解所需的参数
- 对应关系:方法名 - 参数名,方法的返回值 - 参数类型
- 返回值的限制:只能是基本类型、String、Class、Enumeration、Annotation及其一维数组
- 方法的限制:这些方法
没有方法体、没有参数、不能抛异常
,并且只能用public abstract进行修饰
- 可以用
default
来指定默认值 - 如果只有一个参数,可以用
value()
代替(在使用的时候无需写参数名) - 可以利用
classInfo.author()
来获取参数值
解析Annotation
完成自定义Annotation后,我们还需要知道,针对这些注解,我们要做哪些相关的处理,这就涉及到了Annotation的解析操作。
解析Annotation,通常分为:对运行时Annotation的解析、对编译时Annotation的解析;
解析Annotation,其实就是如何从代码中找到Annotation,通常我们的做法是:
- 用
反射
的方式获取Annotation,运行时Annotation的解析方式 - 借助
apt
工具获取Annotation,编译时Annotation的解析方式 - 另外如果我们需要生成额外的代码、文件,则还需要借助
JavaPoet API
反射
反射的解析方式,通常运用在运行时Annotation的解析。
反射是指:利用Class、Field、Method、Construct等reflect对象,获取Annotation:
-
field.getAnnotation(Annotation.class)
:获取某个Annotation -
field.getAnnotations()
:获取所有的Annotation -
field.isAnnotationPresent(Annotation.class)
:是否存在该Annotation
这样,我们就可以在程序运行过程中,动态的获取Annotation的信息。
借助反射进行解析,这在一定程度上会影响程序性能
APT工具
概念
APT:是一个注解处理工具 Annotation Processing Tool
作用:利用apt,我们可以找到源代中的注解,并根据注解做相应的处理
- 根据注解,
生成
额外的源文件或其他文件 -
编译
生成的源文件和原来的源文件,一起生成class文件
利用APT,在编译时生成额外的代码,不会影响性能,只是影响项目构建的速度
Android Studio配置
使用apt工具前,需要对gradle做一些基本的配置:
1.在project的build.gradle中,添加apt的依赖
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
2.在module的build.gradle中,使用apt插件
apply plugin: 'com.neenbedankt.android-apt'
JavaPoet
JavaPoet:是一个自动生成Java代码的Java API。
在使用前,我们需要导入相关的依赖:
compile 'com.squareup:javapoet:1.7.0'
JavaPoet主要有以下几个关键类:
-
JavaFile
:生成Java文件 -
TypeSpec
:定义类 -
MethodSpec
:定义方法
MethodSpec
MethodSpec主要用来定义方法。
MethodSpec mainSpec = MethodSpec.methodBuilder("main") // 方法名
.addModifiers(Modifier.PUBLIC, Modifier.STATIC) // 修饰符
.addParameter(String[].class, "args") // 参数
.returns(TypeName.VOID) // 返回值
.addStatement("$T.out.println($S)", System.class, "Hello,World!!") // 具体代码
.build();
// 添加代码的方法
mainBuilder.addCode("System.out.println(\"Hello,World!\")");
mainBuilder.addStatement("$T.out.println($S)",System.class,"Hello, World!");
mainBuilder.addStatement("activity.$L= ($T) activity.findViewById($L)",
element, ClassName.get(member.asType()), injectViewAnno.value() );
// 这里的element表示一个成员变量, injectViewAnno是一个Annotation, value是注解的参数值
方法名:
methodBuilder()
修饰符:addModifiers(Modifier)
返回值:return(TypeName)
参数:addParameter(Class, name)
添加代码:addStatement
末尾会自动添加换行符,addCode
末位不会自动添加换行符
-$T
:表示需要import的类
-$S
:表示字符串,会自动添加双引号
-$L
:表示变量,不带双引号
TypeSpec
TypeSpec主要用来定义类。
// 外部类
TypeSpec typeSpec = TypeSpec.classBuilder("HelloWorld") // 类名
.addModifiers(Modifier.PUBLIC, Modifier.FINAL) // 修饰符
.addMethod(mainSpec) // 方法
.build();
// 内部类
TypeSpec typeSpec = TypeSpec.anonymousClassBuilder("innerClass") // 内部类的类名
.addSuperinterface(OutClass.class) // 外部类的Class
.build();
JavaFile
JavaFile是真正生成Java文件的核心类。
try {
JavaFile javaFile = JavaFile.builder("com.example.seven", typeSpec).build(); // 指定包名+类
javaFile.writeTo(processingEnv.getFiler()); // 固定写法
} catch (IOException e) {
e.printStackTrace();
}
运行时Annotation
运行时Annotation,是指@Retention为RetentionPolicy.RUNTIME的注解。
对于这类注解,我们通常配合反射
进行解析。
运行时Annotation的解析,需要借助反射进行解析,这在一定程度上会影响程序性能。
早期的依赖注入框架,大都属于运行时处理。
示例代码
public static void main(String[] args) {
try {
Class clazz = Class.forName("com.example.Runtime");
for (Method method : clazz.getMethods()) {
MethodAnno methodAnno = method.getAnnotation(
MethodAnno.class);
if (methodAnno != null) {
System.out.println("method name:" + method.getName());
System.out.println("method author:" + methodAnno.author());
System.out.println("method version:" + methodAnno.version());
System.out.println("method date:" + methodAnno.date());
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
编译时Annotation
编译时Annotation,是指@Retention为RetentionPolicy.CLASS的注解。
对于这类注解,我们通常借助apt工具
、注解处理器Processor
进行解析。
编译时Annotation,利用apt工具,在编译时,自动生成一些额外的代码,相比运行时Annotation更加高效。
定义一个编译时Annotation的标准流程如下:
- 定义注解:@interface
- 定义注解处理器:重写AbstractProcessor的2个方法
- 实现注解处理器:JavaPoet
- 注册注解处理器:@AutoService
- 使用注解,build工程
下面,我们将一一进行讲解
1. 定义注解
参考,上面的【自定义注解】部分
2. 定义注解处理器
概念
注解处理器
是一个继承自AbstractProcessor
的一个Java类,内部主要定义了:
- 要处理的注解是谁:通过
getSupportedAnnotationTypes
方法进行指定 - 如何处理这个注解:通过
process
方法进行指定
注意:AbstractProcessor只能用在Java项目中,在Android项目中无法使用,因为Android内部删除了这个类;因此如果想要在Android项目中,使用这个类,需要新建一个
Java Library
库。
示例代码
@AutoService(Processor.class)
public class AutoCreateProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
// 返回的Set集合,是注解类ClassName的集合
// AutoCreate是一个自定义的Annotation
return Collections.singleton(AutoCreate.class.getCanonicalName());
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 利用JavaPoet进行相关处理
return false;
}
}
解释
getSupportedAnnotationTypes
- 这个方法主要用来指定,当前的注解处理器,需要处理哪些Annotation
- 这些Annotation,将会通过一个
Set集合
的形式返回 - Set集合内部存储的是,所有注解的类名
className
process
- 这个方法主要用来定义,针对这些注解,我们要做哪些相应的处理
- 通常,我们会在方法内部,利用JavaPoet创建相关的Java类、或是XML文件
- 返回值表示,当前Processor是否接收这组Annotation,如果接收,后续其他的Processor将不会继续处理
3. 实现注解处理器
实现注解处理器,主要是利用JavaPoet
对process部分的代码进行完善补充,在下一个博客中,我们将会详细介绍。
4. 注册注解处理器
注册注解处理器,就是告诉APT这个类是一个Annotation Processor
我们可以借助AutoService
进行快速注册:
- 添加AutoService的依赖
- 在类上注解 @AutoService(Processor.class),完成注册
compile 'com.google.auto.service:auto-service:1.0-rc2'
@AutoService(Processor.class)
5. build工程
完成以上步骤,我们build下工程,就会在build目录下自动生成对应的java/class文件
- java文件在build/generated文件夹下
- class文件在build/intermediates文件夹下
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。