从 JDK 5 开始,Java 增加了注解,注解是代码里面的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行一些相应的处理。通过使用注解,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证、处理或者进行部署。
注解可以分为标准注解和元注解。
标准注解是 JDK 自带的注解。元注解是用来注解其他注解的注解。
标准注解由以下 4 种:
除了标准注解,还有元注解,它用来注解其他注解,从而创建新的注解。元注解有以下几种:
@Target 注解取值是一个 ElementType 类型的数组,其中有以下几种取值,对应不同的对象范围。
@Retention 注解有 3 种类型,分别表示不同级别的保留策略。
定义注解使用 @interface 关键字。
public @interface Swordsman {
String name() default "zhaomin";
int age() default 18;
}
定义注解后,可以在程序中使用该注解。
@Swordsman(name = "zhangwuji", age = 20)
public class AnnotationTest {
}
可以看出注解只有成员变量,没有方法。注解的成员变量在注解定义中以无形参的方法表示。返回值定义了成员变量的类型。
使用注解时,可以给成员变量指定值。也可以在定义注解时使用 default 关键字指定默认值。如果有默认值,可以在使用注解时不为成员变量赋值。
可以用 @Retention 来设定注解的保留策略。这 3 个策略的生命周期长度为 SOURCE < CLASS < RUNTIME 。生命周期短的能起作用的地方,生命周期长的也一定能起作用。一般如果需要在运行时去动态获取注解信息,那么只能用 RetentionPolicy.RUNTIME;如果要在编译时进行一些预处理操作,比如生产一些辅助代码,就用 RetentionPolicy.CLASS;如果只是做一些检查的操作,就用 RetentionPolicy.SOURCE。
@Override 注解是源码级别注解(RetentionPolicy.SOURCE):
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
Retrofit 的 @GET 注解是运行时注解(Retention(RUNTIME)):
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
String value() default "";
}
如果将 @Retention 的保留策略设定为 RetentionPolicy.CLASS,这个注解就是编译时注解,如下所示:
@Retention(RetentionPolicy.CLASS)
public @interface Swordsman {
String name() default "zhaomin";
int age() default 18;
}
如果没有处理注解的工具,那么注解也不会有太大的作用。对于不同的注解有不同的注解处理器。虽然注解处理器的编写千变万化,但是也有处理标准。
针对运行时注解会采用反射机制处理,针对编译时注解会采用 AbstractProcessor 来处理。
处理运行时注解需要用到反射机制。定义运行时注解如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Get {
String value() default "";
}
接着使用 Get 注解如下:
public class AnnotationTest {
@Get(value = "http://ip.taobao.com/59.108.54.37")
public String getIpMsg() {
return "";
}
@Get(value = "http://ip.taobao.com/")
public String getIp() {
return "";
}
}
接下来写一个简单的注解处理器,用来获取 Get 注解的值。
public class AnnotationProcessor {
public static void main(String[] args) {
Method[] methods = AnnotationTest.class.getDeclaredMethods();
for (Method m : methods) {
Get get = m.getAnnotation(Get.class);
System.out.println(get.value());
}
}
}
以上代码通过 getDeclaredMethods 获取类的方法,通过 getAnnotation 获取方法的注解,最后调用 Get 的 value 方法返回注接的成员变量的值。
输出如下:
http://ip.taobao.com/59.108.54.37
http://ip.taobao.com/
首先新建一个叫做 annotations 的 Java Library 存放注解:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value() default 1;
}
然后新建一个叫做 processor 的 Java Library 存放注解处理器:
public class ClassProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Messager messager = processingEnv.getMessager();
for (Element ele : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
if (ele.getKind() == ElementKind.FIELD) {
messager.printMessage(Diagnostic.Kind.NOTE, "printMessage:" + ele.toString());
}
}
return true;
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(BindView.class.getCanonicalName());
return annotations;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
在 Java 7 以后,也可以用注解的形式代替 getSupportedAnnotationTypes 和 getSupportedSourceVersion。即 @SupportedAnnotationTypes 和 @SupportedSourceVersion 注解。
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.caoshen.annotations.BindView")
public class ClassProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
...
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
}
并在 processor 的 build.gradle 配置依赖:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':annotations')
}
在 processor 的 main 目录下新建 resources 目录,然后添加一个 META-INF/services 目录。
在 META-INF/services 目录下新建一个名叫 javax.annotation.processing.Processor 的文件。
文件内容如下:
com.caoshen.processor.ClassProcessor
ClassProcessor 用来表示之前定义的注解处理器。
如果觉得上一步的注册 service 步骤麻烦,可以使用 google 的 auto service 自动注册。
在 processor 的 build.gradle 配置依赖:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':annotations')
// auto service
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
}
注意这里的依赖要加上 annotationProcessor 这一行,不然无法生成 META-INF/services 目录以及里面的注解处理器。
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
然后在注解处理器类添加 @AutoService 注解如下:
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.caoshen.annotations.BindView")
public class ClassProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
...
}
...
}
生成的 javax.annotation.processing.Processor 文件在以下目录
processor\build\classes\java\main\META-INF\services
这样就可以避免手动注册。
在 app 模块的 build.gradle 依赖注解和注解处理器
dependencies {
...
// annotation
implementation project(':annotations')
annotationProcessor project(':processor')
}
在 AnnotationActivity 使用注解如下:
public class AnnotationActivity extends Activity {
@BindView(value = R.id.tv_text)
TextView textTv;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_annotations);
}
}
重新 build 项目,在 Run 窗口会打印出 @BindView 注解对应的 TextView 的名称,即 textTv。
输出如下:
注: printMessage:textTv