1. 介绍

Java注解是附加在代码中的一些元信息,用于编译和运行时进行解析和使用,起到说明、配置的功能。注解不会影响代码的实际逻辑,仅仅起到辅助性的作用。注解作为一种重要的技术已经在主流的库中广泛使用。XML和注解都可以用来描述元数据。假如你想为应用设置很多的常量或参数,这种情况下,XML是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用Annotation会更好一些,因为这种情况下需要注解和方法紧密耦合起来,开发人员也必须认识到这点。可见,XML和注解各有自己的用武之地。

一般来说注解的主要作用就是:

  1. 描述元数据
  2. 完成重复性工作: 注解的这个功能也越来越重要了。对于代码里面一些重复性工作,或者通用的横切逻辑,都可以采用注解来实现,使得程序清爽整洁。

定义注解的注意点:

  1. 注解的定义类似于接口的定义,使用@interface来定义
  2. 定义一个方法即为注解类型定义了一个元素,方法的声明不允许有参数或throw语句,返回值类型被限定为原始数据类型、字符串String、Class、enums、注解类型,或前面这些的数组,方法可以有默认值。
  3. 注解并不直接影响代码的语义,但是他可以被看做是程序的工具或者类库。它会反过来对正在运行的程序语义有所影响。注解可以从源文件、class文件或者在运行时通过反射机制多种方式被读取。 #2. 注解实现原理 ##2.1 元注解 在说明注解如何实现时,我们先来说明下元注解的含义。元注解,就是“注解”本身的元信息,也就是注解的注解。

元注解主要包括如下四种(在java.lang,annotation中提供)

注解 说明
@Target 定义注解的作用目标
@Retention 定义注解的保留策略。RetentionPolicy.SOURCE:注解仅存在于源码中,在class字节码文件中不包含;RetentionPolicy.CLASS:默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得;RetentionPolicy.RUNTIME:注解会在class字节码文件中存在,在运行时可以通过反射获取到。
@Document 说明该注解将被包含在javadoc中
@Inherited 说明子类可以继承父类中的该注解

可作为@Target目标的类型主要有以下类型:

Target类型 说明
ElementType.TYPE 接口、类、枚举、注解
ElementType.FIELD 字段、枚举的常量
ElementType.METHOD 方法
ElementType.PARAMETER 方法参数
ElementType.CONSTRUCTOR 构造函数
ElementType.LOCAL_VARIABLE 局部变量
ElementType.ANNOTATION_TYPE 注解
ElementType.PACKAGE

2.2 定义注解

之前我写了一个统计程序运行时间的动态代理程序。具体见JAVA中的动态代理。其作用就是每次程序运行时打印下开始和结束程序的时间戳信息以及运行耗时。现在将演示如何通过注解来完成动态代理的功能。

首先我们来定义注解@StartMonitor:

package com.best.kami.example.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Wan Kaiming on 2016/11/16
 * @version 1.0
 */

//注解作用目标是方法

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface StartMonitor {
    //注解有个默认成员name,默认值为类名称
    //除了name,还可以定义自己所需要的注解属性,都可以在运行时通过反射取得


    //定义注解的成员

}

2.3 注解处理器

注解如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建使用注解处理器。自定义注解处理器,才可以让注解发挥其独特的功能。

Java在java.lang.reflect 包下的AnnotatedElement接口可以用来访问是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的方法来访问Annotation信息。

PS: 从JAVA6开始,注解处理器相关功能才开始比较完善。

自定义处理器一些常用的功能都由javax.annotation.processing.AbstractProcessor这个类给出了抽象实现。因此自定义注解处理器需要继承该抽象类

自定义的注解处理器可以用以下三个注解来配置自己:

注解名称 说明
javax.annotation.processing.SupportedAnnotationTypes 这个注解用来注册注解处理器要处理的注解类型。有效值为完全限定名(就是带所在包名和路径的类全名)-通配符(此次英语原文为Wildcards,就是?这个符号代表的类型。比如说List<? extends String>)
javax.annotation.processing.SupportedSourceVersion 这是用来注册注解处理器要处理的源代码版本。JAVA SE版本,现在取值RELEASE_8 代表1.8
javax.annotation.processing.SupportedOptions 这个注解用来注册可能通过命令行传递给处理器的操作选项。

下面来定义下注解处理器。这样每次遇到对应注解的时候会自动使用注解处理器。我们这里使用的注解是@StartMonitor

后面的懒得写了,原谅我。不过代码我已经写好了,感兴趣可以参考Java语言使用注解处理器生成代码这个系列文章。我也是按照这个做下来的。这边贴的是它的github,可以看README.md中有给具体的译文:

我的源码放在:我的github

用这种比较原生的方式,会发现,为了使得注解处理器生效,我必须用命令行运行:
例如我的代码运行就必须这样:

javac -cp kami-tools-examples-annotations-1.0-SNAPSHOT.jar;kami-tools-examples-annotations-processors-1.0-SNAPSHOT.jar  ../java/client/AnnotationClient.java

这种方式实在不方便。不过我猜测,可以把注解处理器和注解定义都不单独写成一个构件,而是和用到自定义注解的程序放在一个构件里面,就可能不需要这么做了。不过那样耦合度太高了。所以后续还是考虑使用Spring来自定义注解。感兴趣可以看后续文章吧。

参考资料(推荐阅读):

  1. 深入理解Java:注解(Annotation)基本概念
  2. 深入理解Java:注解(Annotation)自定义注解入门
  3. 深入理解Java:注解(Annotation)--注解处理器
  4. [译]使用注解处理器生成代码-2 注解处理器
  5. Java语言使用注解处理器生成代码(文章并附源码)
  6. Annotation实战【自定义AbstractProcessor】 7.Annotation Processor compilation in IntelliJ IDEA