概要
在Java和Android的开发中,自从注解问世,越来越受开发者们所青睐,可以说,注解大大简化了开发者的开发作业。无论是运行时注解,还是编译时注解,都被广泛的使用着。而且,市面上产生了好多依赖注解而崛起的开源库,Google官方更是为了Android而推出了Support Annotation。
如何定义注解
注解是Java引入的特性,通过在Java源代码中引入注解标签,可以在编译或者运行期间做一些额外的事情。每个注解都有严格的格式来定义:@interface用来声明注解,接口的方法对应于注解的元素,下面是一个完整的注解定义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Bind {
@IdRes int[] value();
}
@interface声明会创建一个真实的java接口,与其它接口一样,会被编译成.class文件;注解接口中的元素实际上是方法声明(如上面的value()方法),这个方法声明没有参数,没有throws语句,也不能使用泛型。
标准注解
Java中默认定义的注解称之为标准注解,他们在java.lang、java.lang.annotation和javax.annotation包中,根据应用的场景,可以分为三类:
1、与编译相关的注解:给编译器使用
- @Override:编译器会检查被注解的方法是否重载了一个父类的方法,如果没有,编译器会给出错误提示;
- @Deprecated:用来修饰任何不被鼓励使用或者放弃使用的方法或属性;
- @FunctionalInterface:用来修饰接口,表示对应的接口是带单个方法的函数式接口;
- @SafeVarargs:用于方法或构造函数,用来断言不定长参数可以安全使用;
- @SuppressWarnings:用来抑制某种类型的警告;
- @Generated:一般用来给代码生成工具使用,表示这段代码是自动生成的,而不是开发者自己编写的,通常这段代码不建议修改;
2、资源相关的注解:一共有4个,一般用于JavaEE,Android领域不会用到,具体如下:
- @PostConstruct:用在控制对象生命周期的环境中,表示在构造函数之后应立即调用被修饰的方法;
- @PreDestroy:表示在删除一个被注入的对象前应立即调用被该注解修饰的方法;
- Resource:用于Web容器的资源注入,表示单个资源;
- Resources:用于Web容器的资源注入,表示一个资源;
3、元注解:用于定义和实现注解的注解,一个有5种:
- @Target:这个注解的元素是一个ElementType类型的数组,表示这个注解所适用的对象范围,总共有10种类型,分别如下:
public enum ElementType {
TYPE, //类或接口(包含eumn和注解类型的接口)
FIELD, //实例变量
METHOD, //方法
PARAMETER, //参数
CONSTRUCTOR, //构造函数
LOCAL_VARIABLE, //局部变量
ANNOTATION_TYPE, //注解类型声明
PACKAGE, //包
TYPE_PARAMETER, //类型参数
TYPE_USE; //类型的用途
private ElementType() {
}
}
- @Retention:用于指定注解的访问范围,即在什么级别保留注解,有三种类型,分别为:
public enum RetentionPolicy {
SOURCE, //源码级注解,只会保留在.java文件
CLASS, //编译级注解,编译后会保留在.java和.class文件,运行时被虚拟机丢弃,默认选项
RUNTIME; //运行时注解,在运行期间也会保留注解,可以通过反射机制读取注解信息
private RetentionPolicy() {
}
}
- @Documented:表示被修饰的注解应该被包含在被注解项的文档中;
- @Inherited:表示该注解可以被之类继承;
- @Repeatable:表示这个注解可以被应用多次;
编译时注解
编译时注解能够让编译器自动处理Java源文件并生成更多的代码、配置文件、脚本或其它可能想要生成的东西。这些操作通过注解处理器完成。Java编译器集成了注解处理器,通过在编译期间调用javac -processor命令可以调用注解处理器,它能够允许我们实现编译时注解的功能,从而提高函数库的性能。
1、定义注解处理器
编译期间,编译器会定位到java源文件中的注解,然后由注解处理器进行处理,需要注意的是,注解处理器只会生成新的源文件,而不会去修改一个已经存在的源文件。注解处理器通常继承AbstractProcessor类并实现process()方法,同时需要指定这个处理器能够处理的注解类型以及支持的Java版本。如下:
package com.example;
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
@Override
public Set<String> getSupportedAnnotationTypes() { }
@Override
public SourceVersion getSupportedSourceVersion() { }
}
init(ProcessingEnvironment env)
: 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements,Types和Filer。getSupportedAnnotationTypes()
:指定这个注解处理器能够处理的注解类型,返回一个可以支持的注解类型的字符串集合;从java7开始,可以使用@SupportedAnnotationTypes({})注解来代替该方法;getSupportedSourceVersion()
:指定注解处理器适用的Java版本,通常返回SourceVersion.latestSupported()即可,当然也可以返回指定版本的Java,如SourceVersion.RELEASE_6;从java7开始,可以使用@SupportedSourceVersion(Java_Version)注解来代替该方法;process(Set<? extends TypeElement> annoations, RoundEnvironment env)
:在该方法中实现注解处理器的具体业务逻辑,根据env可以得到具体的被注解元素,然后可以编写处理注解的代码最终生成所需的Java源文件;
2、注册注解处理器
注解处理器定义好之后,为了让javac -processor能够对其进行处理,我们需要将注解处理器打包成一个.jar文件,同时,需要在jar文件中增加一个名为javax.annotation.processing.Processor的文件来指明jar文件中都包含哪些注解处理器,这个文件的最终路径位于jar文件根目录的META-INF/services目录中;javax.annotation.processing.Processor文件的内容是注解处理器的全路径名,如果存在多个,以换行进行隔离,如下:
com.heyha.processor.JsonAnnotationProcessor
现在,我们知道了该文件的最终路径,那么它的源文件的路径是怎样的呢?只需要在src/main/java同级目录中新建一个名为resources的目录,将javax.annotation.processing.Processor文件放进去即可。
此外,注解处理器所在的工程必须是Java Library类型,而不能是Android Library类型,因为在编译时注解可能会用到javax包里面的内容。手动执行这个注册过程是很繁琐的,为了简化该操作,google官方提供了AutoService函数库,使用方式是引入该函数库后,在定义Processor时添加AutoService注解标记即可。如下:
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({
//注解处理器支持的所有注解全名字符串类型的集合
})
public class MyProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
}
还有一点需要注意,注解处理器所在的jar包只在编译期间起作用,运行时不起作用,所以在使用该jar包时需要在build.gradle中使用provided方式引入,而不是compile方式。
注:本篇文章是为了记录学习Android注解的使用,在此感谢顾老师的《Android高级进阶》。