本章知识点

  • JDK内置系统注解
  • 元注释以及自定义注释
  • 注解解析器实现
  • Android中的专用注解

JDK内置系统注解

Java提供了三种内建注解。

@Override
这个估计开发中是最常见的一个注解了,它用于表示所注解的方法是一个用于覆盖父类的一个方法,一旦存在任何的拼写错误导致方法签名对不上被覆盖的方法,编译器就会发出错误提示。
@Deprecated
这个一般用于标记某个方法不在使用了,通过这个注释,开发者可以了解到哪些接口是处于遗弃状态的。
@SuppressWarnings
这个注释用于忽视某个警告信息。

元注释以及自定义注释

Java中提供了四种元注释用于定义自定义注释,如下所示:

@Target 表示该注解可以用在什么地方,由ElementType枚举定义 ,当注解未指定Target值时,该注解可以使用任何元素之上

PACKAGE:包声明
TYPE:类、接口 注解类型,enum声明
ANNOTATION_TYPE:注解声明(应用于另一个注解上)
FIELD:域声明(包括enum实例)
CONSTRUCTOR:构造器的声明
METHOD:方法声明
LOCAL_VARIABLE:局部变量声明
PARAMETER:参数声明
TYPE_PARAMETER:类型参数声明(1.8新加入)
TYPE_USE:类型使用声明(1.8新加入)

@Retention 表示需要在什么级别保存该注解信息,由RetentionPolicy枚举定义,当注解未定义Retention值时,默认值是CLASS

SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机(JVM)中)
RUNTIMEVM将在运行期也保留注解信息,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息)

@Documented 表示注解会被包含在javaApi文档中
@Inherited 允许子类继承父类的注解,表示注解类型能被自动继承。 如果一个类使用了 @Inherited 类型的注解,则此类的子类也将含有该注解。

自定义注解语法:

import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface AnatationTest {
}

需要注意的是注解也将会编译成class文件,我们通过javac将上述的注解编译为字节码,然后使用javap对其进行反编译,将会得到如下的代码:

public interface AnatationTest extends java.lang.annotation.Annotation {
}

也就是说使用@interface 关键字声明一个注解,它会自动继承 java.lang.annotation.Annotaion 接口。

自定义注解的时候需要注意如下几点:

  • 参数成员访问修饰符只能使用 public 或者 default 这个和枚举是一样的
  • 注解方法不能有参数。
  • @interface 里面的每一个方法表示声明了一个可配置的参数,方法名即位参数名。返回值类型就是参数的类型,下面是能够允许的类型:
    所有基本类型(int,float,boolean,byte,double,char,long,short
    String
    Class
    enum
    Annotation
    以及上述类型所组成的 数组
  • 如果需要声明默认值可以使用default 关键字。
  • 注解元素必须有确定的值,要么在定义注解元素时默认值指定,要么使用此注解时指定。非基本类型注解元素的值不可为 null

注解解析器实现

注释的解析有两种方式:

  • 运行时解析
    运行时注解指的是@Retention的值为RUNTIME的注解。java.lang.reflect包中有一个AnnotatedElement接口,这个接口定义了用于获取注解信息的几个方法:
    getAnnotation(Class annotationClass) 当存在该元素的指定类型注解,则返回相应注释,否则返回null
    getAnnotations() 返回此元素上存在的所有注解
    getDeclaredAnnotations() 返回直接存在于此元素上的所有注解。
    isAnnotationPresent(Class<? extends Annotation> annotationClass) 当存在该元素的指定类型注解,则返回true,否则返回false
    使用这些接口就可以在运行时解析对应的注释。

下面是运行时解析的例子:

@Target(ElementType.PACKAGE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PackageAnnotation {
String desc() default "";
}


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeAnnotation {
String desc() default "";
}


@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
public @interface ConstructAnnotaion {
String desc() default "";

}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldAnnotation {
String desc() default "";
}


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAnnotation {
String desc() default "";
}

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamAnnotation {
String desc() default "";
}

@TypeAnnotation(desc = "This is TypeAnnotation")
public class TestClass {
@FieldAnnotation(desc = "This is FieldAnnotation")
public String mTestField = "mTestField";

@ConstructAnnotaion(desc = "This is ConstructAnnotaion")
public TestClass() {
}

@MethodAnnotation(desc = "This is MethodAnnotation")
public void method(@ParamAnnotation(desc = "This is ParamAnnotation")String param) {
String localvalue = "";
System.out.println("method!!!!!"+param);
}
}

package-info.java

@PackageAnnotation(desc = "This is a package annotation!")
package com.javabase.annotationbase.testclass;
import com.javabase.annotationbase.annotations.PackageAnnotation;
public class RuntimeAnnotationProcessor {

public void testPackageAnnotation(Class<?> clzz) {
Package packageName = clzz.getPackage();
if(packageName.isAnnotationPresent(PackageAnnotation.class)) {
PackageAnnotation packageAnnotation = packageName.getAnnotation(PackageAnnotation.class);
System.out.println(packageAnnotation.desc());
}
}

public void testClassAnnotation(Class<?> clz) {
if (clz.isAnnotationPresent(TypeAnnotation.class)) {
TypeAnnotation typeAnnotation = clz.getAnnotation(TypeAnnotation.class);
System.out.println(typeAnnotation.desc());
}
}

public void testConstructureAnnotation(Class<?> clz) {
Constructor[] constructors = clz.getConstructors();
for(Constructor constructor : constructors) {
if(constructor.isAnnotationPresent(ConstructAnnotaion.class)) {
ConstructAnnotaion annotation = (ConstructAnnotaion) constructor.getAnnotation(ConstructAnnotaion.class);
System.out.println(annotation.desc());
}
}
}

public void testFieldAnnotation(Class<?> clz) {
Field[] fields = clz.getFields();
if(fields != null && fields.length > 0) {
for (Field field: fields) {
if(field.isAnnotationPresent(FieldAnnotation.class)) {
FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
System.out.println(fieldAnnotation.desc());
}
}
}
}

public void testMethodAnnotation(Class<?> clz) {
Method[] methods = clz.getMethods();
if (methods != null && methods.length > 0) {
for (Method method : methods) {
if (method.isAnnotationPresent(MethodAnnotation.class)) {
MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class);
try {
method.invoke(clz.newInstance(), "");
} catch (IllegalAccessException | InvocationTargetException
| InstantiationException e) {
e.printStackTrace();
}
System.out.println(methodAnnotation.desc());
}
}
}
}

public void testParamAnnotation(Class<?> clz) {
Method[] methods = clz.getMethods();
if (methods != null && methods.length > 0) {
for (Method method : methods) {
if (method.isAnnotationPresent(MethodAnnotation.class)) {
Parameter[] parameters = method.getParameters();
if (parameters != null && parameters.length > 0) {
for (Parameter parameter : parameters) {
if(parameter.isAnnotationPresent(ParamAnnotation.class)) {
ParamAnnotation paramAnnotation = parameter.getAnnotation(ParamAnnotation.class);
System.out.println(paramAnnotation.desc());
}
}
}
}
}
}
}

}

  • 编译时解析
    编译时注解指的是@Retention的值为CLASS的注解。对于这类注解的解析,我们需要自定义一个派生自 AbstractProcessor的“注解处理类”并重写process 函数。

  • APT(Annotation Processing Tool)是一种处理注释的工具
    Annotation处理器在处理注释时可以根据源文件中的Annotation生成额外的源文件和其它的文件,APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。

  • 整个注解处理器的可以由如下几个部分构成:
    注解处理器(AbstractProcess)+代码处理(JavaPoet)+处理器注册(AutoService)+apt

  • 整个过程分成如下几个部分:
    1.定义注解(如@automain)
    2.定义注解处理器
    3.在处理器里面完成处理方式,通常是生成java代码,这个就需要用到代码处理。
    4.注册处理器,这里就需要用到处理器注册。
    5.利用APT进一步处理。

建立一个APT 项目,并添加apt依赖:

buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

添加apt 插件

//添加APT plugin
apply plugin: 'com.neenbedankt.android-apt'

新建一个model 命名为compiler 用于存放注解处理器:
并添加autoservice 和javapoet依赖:

apply plugin: 'java'

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.squareup:javapoet:1.8.0'
compile 'com.google.auto.service:auto-service:1.0-rc2'
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

创建AnnotationProcessor

package com.example;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.Collections;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;

@AutoService(Processor.class)
public class AnnotationProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(TestAnnotation.class.getCanonicalName());
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}

这时候编译后会在/app/build/generated/source/apt/debug/目录下生成HelloWorld.java文件

参考材料
http://www.race604.com/annotation-processing/
http://www.jianshu.com/p/1942ad208927
http://blog.csdn.net/lmj623565791/article/details/43452969
https://github.com/taoweiji/DemoAPT?utm_source=tuicool&utm_medium=referral
http://www.cnblogs.com/lbangel/p/3523741.html
http://www.jianshu.com/p/94979c056b20
http://alighters.com/blog/2016/05/10/apt-code-generate/

Contents
  1. 1. JDK内置系统注解
  2. 2. 元注释以及自定义注释
  3. 3. 自定义注解语法:
  4. 4. 注解解析器实现