注解-学习笔记
一、注解
从JDK 5 开始,Java 新增注解,注解是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。
通过使用注解,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证、处理或者进行部署。
1.1 注解分类
注解分为标准注解和元注解。
1.1.1 标准注解
标准注解有以下 4 种。
• @Override:对覆盖超类中的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编译器会发出错误警告。
• @Deprecated:对不鼓励使用或者已过时的方法添加注解,当编程人员使用这些方法时,将会在编译时显示提示信息。
• @SuppressWarnings:选择性地取消特定代码段中的警告。
• @SafeVarargs:JDK 7 新增,用来声明使用了可变长度参数的方法,其在与泛型类一起使用时不会出现类型安全问题。
1.1.2. 元注解
元注解用来注解其他注解,从而创建新的注解。元注解有以下几种。
• @Targe:注解所修饰的对象范围。
• @Inherited:表示注解可以被继承。
• @Documented:表示这个注解应该被 JavaDoc 工具记录。
• @Retention:用来声明注解的保留策略。
• @Repeatable:JDK 8 新增,允许一个注解在同一声明类型(类、属性或方法)上多次使用。
其中@Targe 注解取值是一个 ElementType 类型的数组,其中有以下几种取值,对应不同的对象范围。
• ElementType.TYPE:能修饰类、接口或枚举类型。
• ElementType.FIELD:能修饰成员变量。
• ElementType.METHOD:能修饰方法。
• ElementType.PARAMETER:能修饰参数。
• ElementType.CONSTRUCTOR:能修饰构造方法。
• ElementType.LOCAL_VARIABLE:能修饰局部变量。
• ElementType.ANNOTATION_TYPE:能修饰注解。
• ElementType.PACKAGE:能修饰包。
• ElementType.TYPE_PARAMETER:类型参数声明。
• ElementType.TYPE_USE:使用类型。
其中@Retention 注解有 3 种类型,分别表示不同级别的保留策略。
• RetentionPolicy.SOURCE:源码级注解。注解信息只会保留在.java 源码中,源码在编译后,注解信息被丢弃,不会保留在.class 中。
• RetentionPolicy.CLASS:编译时注解。注解信息会保留在.java 源码以及.class 中。当运行 Java 程序时,JVM 会丢弃该注解信息,不会保留在 JVM 中。
• RetentionPolicy.RUNTIME:运行时注解。当运行 Java 程序时,JVM 也会保留该注解信息,可以通过反射获取该注解信息。
1.2 定义注解
1.2.1 基本定义
定义新的注解类型使用@interface 关键字,这与定义一个接口很像,如下所示:
public @interface AnnotationCreateMan {
}
定义完注解后,就可以在程序中使用该注解:
@AnnotationCreateMan
public class AnnotationTest {
}
1.2.2 定义成员变量
注解只有成员变量,没有方法。注解的成员变量在注解定义中以“无形参的方法”形式来声明,其“方法名”定义了该成员变量的名字,其返回值定义了该成员变量的类型:
public @interface AnnotationCreateMan {
String name();
int age();
}
上面的代码定义了两个成员变量,这两个成员变量以方法的形式来定义。定义了成员变量后,使用该注解时就应该为该注解的成员变量指定值:
@AnnotationCreateMan(name = "Tom", age = 35)
public class AnnotationTest {
@AnnotationCreateMan(name = "Jim", age = 22)
public void fighting() {
}
}
也可以在定义注解的成员变量时,使用 default 关键字为其指定默认值,如下所示:
public @interface AnnotationCreateMan {
String name() default "Jack";
int age() default 25;
}
因为注解定义了默认值,所以使用时可以不为这些成员变量指定值,而是直接使用默认值:
@AnnotationCreateMan(name = "Tom", age = 35)
public class AnnotationTest {
@AnnotationCreateMan
public void fight() {
}
}
1.2.3 定义运行时注解
可以用@Retention 来设定注解的保留策略,这 3 个策略的生命周期长度为 SOURCE < CLASS < RUNTIME 。生命周期短的能起作用的地方,生命周期长的一定也能起作用。一般如果需要在运行时去动态获取注解信息,那只能用 RetentionPolicy.RUNTIME;如果要在编译时进行一些预处理操作,比如生成一些辅助代码,就用 RetentionPolicy.CLASS;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 RetentionPolicy.SOURCE。当
设定为 RetentionPolicy.RUNTIME 时,这个注解就是运行时注解,如下所示:
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationCreateMan {
String name() default "Jack";
int age() default 25;
}
1.2.4 定义编译时注解
同样地,如果将@Retention 的保留策略设定为 RetentionPolicy.CLASS,这个注解就是编译时注解,如下所示:
@Retention(RetentionPolicy.CLASS)
public @interface AnnotationCreateMan {
String name() default "Jack";
int age() default 25;
}
1.3 注解处理器
如果没有处理注解的工具,那么注解也不会有什么大的作用。对于不同的注解有不同的注解处理器。虽然注解处理器的编写会千变万化,但是其也有处理标准,比如:针对运行时注解会采用反射机制处理,针对编译时注解会采用 AbstractProcessor 来处理。
1.3.1 运行时注解处理器
处理运行时注解需要用到反射机制。首先我们要定义运行时注解,如下所示:
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
String value() default "";
}
上面的代码是 Retrofit 中定义的@GET 注解。其定义了@Target(METHOD),这等效于@Target(ElementType.METHOD),意味着 GET 注解应用于方法。接下来应用该注解,如下所示:
public class AnnotationTest {
@GET(value = "http://ip.taobao.com/59.108.54.3")
public String getIpMsg() {
return "";
}
@GET(value = "http://ip.taobao.com/")
public String getIp() {
return "";
}
}
上面的代码为@GET 的成员变量赋值。接下来写一个简单的注解处理器,如下所示:
public class AnnotationProcessor {
private static final String TAG = "AnnotationProcessor";
public static void initAnnotation() {
Method[] methods = AnnotationTest.class.getDeclaredMethods();
for (Method m : methods) {
GET get = m.getAnnotation(GET.class);
Log.v(TAG, get.value());
}
}
}
上面的代码用到了两个反射方法:getDeclaredMethods 和 getAnnotation,它们都属于AnnotatedElement 接口,Class、Method 和 Filed 等类都实现了该接口。调用 getAnnotation 方法返回指定类型的注解对象,也就是 GET。最后调用 GET 的 value 方法返回从 GET 对象中提取元素的值。
调用注解处理器:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AnnotationProcessor.initAnnotation();
}
}
输出结果为:
注解输出结果1.3.2 编译时注解处理器
处理编译时注解的步骤稍微有点多,首先要定义注解。
(1)定义注解
这里首先在项目中新建一个 Java Library 来专门存放注解,这个 Library 名为 annotations。接下来定义注解,如下所示:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value() default 1;
}
上面代码中定义的注解类似于 ButterKnife 的@BindView 注解。
(2)编写注解处理器
我们在项目中再新建一个 Java Library 来存放注解处理器,这个 Library 名为 processor。我们来配置 processor 库的 build.gradle:
apply plugin: 'java'
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile project(':annotations')
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
接下来编写注解处理器 ClassProcessor,它继承 AbstractProcessor,如下所示:
public class ClassProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//TODO 处理注解
return false;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<String>();
annotations.add(BindView.class.getCanonicalName());
return annotations;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
process 方法的实现会在后文讲到,这里首先分别介绍这 4 个方法的作用。
• init:被注解处理工具调用,并输入 ProcessingEnviroment 参数。ProcessingEnviroment提供很多有用的工具类,比如 Elements、TypesFiler 和 Messager 等。
• process:相当于每个处理器的主函数 main(),在这里写你的扫描、评估和处理注解的代码,以及生成 Java 文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。
• getSupportedAnnotationTypes:这是必须指定的方法,指定这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。
• getSupportedSourceVersion :用来指定你使用的 Java 版本,通常这里返回SourceVersion.latestSupported()。 在Java 7 以后,也可以使用注解来代替getSupportedAnnotationTypes 方法getSupportedSourceVersion方法,如下所示:
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.lzw.annotations.BindView")
public class ClassProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
}
但是考虑到 Android 兼容性的问题,这里不建议采用这种注解的方式。接下来编写还未实现的 process 方法,如下所示:
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Messager messager = processingEnv.getMessager();
for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
if (element.getKind() == ElementKind.FIELD) {
messager.printMessage(Diagnostic.Kind.NOTE, "printMessage:"
+ element.toString());
}
}
return true;
}
这里用到 Messager 的 printMessage 方法来打印出注解修饰的成员变量的名称,这个名称会在 Android Studio 的 Gradle Console 窗口中打印出来。
(3)注册注解处理器
为了能使用注解处理器,需要用一个服务文件来注册它。现在我们就来创建这个服务文件。
- 首先在 processor 库的 main 目录下新建 resources 资源文件夹
- 接下来在 resources 中再建立META-INF/services 目录文件夹。
- 最后在 META-INF/services 中创建 javax.annotation.processing.Processor 文件,这个文件中的内容是注解处理器的名称。
这里我们的 javax.annotation.processing.Processor 文件的内容为 com.lzw.annotationprocessor.ClassProcessor。
整个项目的目录结构如图 9-1 所示
注解demo项目结构
如果你觉得前面创建服务文件的步骤比较麻烦,也可以使用 Google 开源的 AutoService,它用来自动生成 METAINF/services/javax.annotation.processing.Processor 文件。首先我们添加该开源库,可以在 File→Project Structure 搜索“auto-service”查找该库并添加,如下图所示。也可以在 processor 的 build.gradle 中直接添加如下代码:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(":annotations")
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
compileOnly 'com.google.auto.service:auto-service:1.0-rc6'
}
最后在注解处理器 ClassProcessor 中添加@AutoService(Processor.class)就可以了:
@AutoService(Processor.class)
public class ClassProcessor extends AbstractProcessor {
...
}
(4)应用注解
接下来在我们的主工程项目(app)中引用注解。首先要在主工程项目的 build.graldle 中引用 annotations 和 processor 这两个库:
dependencies {
...
implementation project(':annotations')
annotationProcessor project(':annotationProcessor')
}
接下来在 MainActivity 中应用注解,如下所示:
public class MainActivity extends AppCompatActivity {
//编译时注解
@BindView(value = 111)
TextView tv_show_name;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//运行时注解
AnnotationProcessor.initAnnotation();
}
}
最后,我们先 Clean Project 再 Make Project,在 Gradle Console 窗口中打印的结果如图所示:
编译时注解结果可以发现编译时会打印出@BindView 注解修饰的成员变量名:tv_show_name。
(5)使用 android-apt 插件(这个问题目前还未解决)
我们的主工程项目(app)中引用了 processor 库,但注解处理器只在编译处理期间需要用到,编译处理完后就没有实际作用了,而主工程项目添加了这个库会引入很多不必要的文件。为了处理这个问题我们需要引入插件 android-apt。它主要有两个作用:
• 仅仅在编译时期去依赖注解处理器所在的函数库并进行工作,但不会打包到 APK 中。
• 为注解处理器生成的代码设置好路径,以便 Android Studio 能够找到它。
接下来介绍如何使用它。首先需要在整个工程(Progect)的 build.gradle 中添加如下语句:
buildscript {
...
dependencies {
...
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
} }
接下来在主工程项目(app)的 build.gradle 中以 apt 的方式引入注解处理器 processor,如下所示:
...
apply plugin: 'com.neenbedankt.android-apt'
...
dependencies {
...
//compile project(':processor')
apt project(':processor')
}