最近在实习的时候接触到公司自主研发的框架,在开发过程中需要使用到各种各样的注解来标注,辅助实现相关的基础功能,为了理解注解的意义以及它的工作机制,故作此博客以整理记录自己所学

 

1、什么是注解?

注解属于Java语言的特性,是在Java5.0引入的新特征 ,位于java.lang.annotation包中 。 

注解的概念

注解是用于给Java代码附加元数据,可在编译时或运行时解析并处理这些元数据。Java代码可以是包名、类、方法、成员变量、参数等,且附加的元数据不会影响源代码的执行。 

我个人的理解:注解不同于注释,注释是帮助人能更好地理解这块代码,而注解是帮助编译器、机器去解析这块代码,具有实际的功能意义。注解就像标签,被注解过的Java代码就像被标注的物品一样,可以通过标签去找到相应的物品进行处理。

注解的使用方法:

@Override
public String toString() {
    return "annotation demo";
}

此处使用的是java.lang.annotation中最常用的注解@Override,一般用于标注重写父类的方法或者实现的接口方法。

以下是java.lang.annotation包下的7个注解:

// 重写父类方法或实现接口方法
@Override

// 标记过时的方法,使用时在编译过程会报错
@Deprecated

// 指示编译器去忽略注解中的警告
@SuppressWarnings

// 标注注解的生命周期,保存在什么状态
@Retention
/**
 * 例如:
 * @Retention(RetentionPolicy.SOURCE) 在编译阶段丢弃,不会写进字节码文件.class文件
 * @Retention(RetentionPolicy.CLASS) 在类加载的时候丢弃,在.class文件中保留,为默认使用方式
 * @Retention(RetentionPolicy.RUNTIME) 在运行时依旧保存
 */

// 标记这些注解是否包含在用户文档中
@Document

// 标记这个注解是在哪种Java成员中
@Target
/**
 * @Target里可以放入如下参数:
 * ElementType.TYPE:用于描述类、接口或enum声明
 * ElementType.FIELD:用于描述实例变量
 * ElementType.METHOD:用于描述方法
 * ElementType.PARAMETER:用于描述参数
 * ElementType.CONSTRUCTOR:用于描述构造函数
 * ElementType.LOCAL_VARIABLE:用于描述局部变量
 * ElementType.ANNOTATION_TYPE:用于描述另一个注释
 * ElementType.PACKAGE:用于记录java文件的package信息
 * ElementType.TYPE_PARAMETER
 * ElementType.TYPE_USE
 * eg:
 * @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
 * 该注解可以标注方法和构造函数
 */

// 标记这个注解是继承于哪个注解类型的
@Inherited

// 忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告
@SafeVarargs

// 标识一个匿名方法或函数式接口
@FunctionalInterface

// 标识某注解可以在同一个声明上使用多次
@Repeatable

2、自定义注解

由于注解的工作机制在对某些常用的非业务性的模块代码有相当大的辅助作用,所以在一个项目中,往往会由架构师将一些简单的通用功能定义成注解,在开发文档中指明注解的使用情形,之后由开发人员直接使用,可减少重复代码,提高开发效率,所以说注解的用途还是非常大的。

自定义注解的格式:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AnnotationDemo {
	/**
	 * 属性
	 * eg: 
	 */
    String key() default "name";    // key的默认值为"name"
    String value();
}

注解的属性可支持的数据类型有:

  1. 基本数据类型(byte, int, long, short, float, double, char, boolean)
  2. String类型
  3. Class类型
  4. enum类型
  5. Annotation类型
  6. 以上类型的数组
     

设置默认值的属性在使用的时候可以无需输入形参,没有设置默认值的属性在使用的时候必须输入形参,如:

@AnnotationDemo(value = "AnnotationDemo")
public class Demo1() {
    /**
     * 代码
     */
}

@AnnotationDemo(key = "className", value = "AnnotationDemo")
class Demo2() {
    /**
     * 代码
     */
}

3、注解的处理机制

我们可以在两个时期对被注解的内容进行处理:运行时处理、编译时处理

运行时处理:

运行时处理是通过Java反射机制获取注解。而Java的反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。通过反射可以提供类名、方法和实例变量对象。所有这些对象都有getAnnotation()这个方法用来返回注解信息。我们需要把这个对象转换为我们自定义的注释(使用 instanceOf()检查之后),同时也可以调用自定义注释里面的方法。

@AnnotationDemo(value = "Demo1")
class Demo1 { }

public class Main {
    public static void main(String[] args) {
        Class demo = Demo1.class;
        AnnotationDemo test = (AnnotationDemo)demo.getAnnotation(AnnotationDemo.class);
        if (test != null) {
            System.out.println("key: " + test.key());
            System.out.println("value: " + test.value());
        }
    }
}

最后输出结果为:

key: name value: Demo1

运行时处理的缺点:

  1. 通过反射会影响运行效率
  2. 如果注解无法保存到运行时,是无法使用运行时处理的,即注解的定义必须是@Retention(RetentionPolicy.RUNTIME)

 

编译时处理:

编译时处理是通过APT技术实现的。

APT处理的过程:

  1. 在编译期扫描.java文件的注解,并传到注解处理器
  2. 注解处理器根据定义好的注解处理方式进行操作
  3. 生成新的java文件

一下代码为注解处理器的代码模板

public class CustomProcessor extends AbstractProcessor {
    /**
    * 初始化注解处理器
    * @param processingEnv APT框架的环境对象,通过该对象可以获取到很多工具类
    */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

    /**
    * 配置该注解处理器需要处理的注解类型
    * @return
    */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }

    /**
    * 配置该注解处理器支持的最新版本
    * @return
    */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return super.getSupportedSourceVersion();
    }

    /**
    * 用于处理注解,并生成.java文件
    * @param annotations
    * @param roundEnv
    * @return
    */
    @Override
    public boolean process(Set<? extends TypeElement> annotations,
                          RoundEnvironment roundEnv) {
        return false;
    }
}

编译时处理的优点:不在运行时进行操作,所以对程序的性能不会产生影响

编译时处理的缺点:

  1. 无法对原来的.java文件进行修改
  2. 生成额外的.java文件
  3. 因为是在编译期进行处理,会影响编译速度