• 注解(Annotation)也被称为元数据(Metadata),用于修饰包、类、方法、属性、构造器、局部变量等。
  • 注解 是源代码的元数据。
  • 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。
  • 在JavaSE中,注解使用比较简单,例如标记过时的功能,忽略警告等。在JavaEE 中注解占据更重要的角色,例如配置切面,代替JavaEE 旧版本中所遗留的繁冗代码和xml配置等
  • 如果一个注解中有一个名称为value的属性(名字必须为value),且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略掉“value=”部分。
  • Java想给程序元素提供元数据支持,于是创造了Annotation来实现这个目标。

    注解定义

  • 注解的关键字 @interface + 元注解 ```java package cn.java.money.annotations.demo1;

import java.lang.annotation.*;

@Documented @Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface MyAnnotation {

}

  1. 字节码
  2. ```java
  3. // class version 52.0 (52)
  4. // access flags 0x2601
  5. public abstract @interface cn/java/money/annotations/demo1/MyAnnotation implements java/lang/annotation/Annotation {
  6. // compiled from: MyAnnotation.java
  7. @Ljava/lang/annotation/Documented;()
  8. @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.CLASS)
  9. @Ljava/lang/annotation/Target;(value={Ljava/lang/annotation/ElementType;.TYPE})
  10. }
  • 注解本质上实现的是 java.lang.annotation.Annotation 接口, 也就是Annotation接口是所有注解的父接口。
    • 接口中的方法
      • Class<? extends Annotation> annotationType();
      • boolean equals(Object obj);
      • int hashCode();
      • String toString();

带成员变量的注解
以无形参的方法形式来声明Annotation的成员变量,方法名和返回值定义了成员变量名称和类型。使用default关键字设置初始值。没设置初始值的变量则使用时必须提供有初始值的变量可以设置也可以不设置

  1. package cn.java.money.annotations.demo2;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. //定义带成员变量注解 MyTag
  7. @Retention(RetentionPolicy.RUNTIME)
  8. @Target(ElementType.METHOD)
  9. public @interface MyTag{
  10. //定义两个成员变量,以方法的形式定义
  11. //方法名和返回值定义了成员变量名称和类型
  12. String name();
  13. int age() default 32;
  14. }
  1. package cn.java.money.annotations.demo2;
  2. import java.lang.reflect.Method;
  3. public class Dog{
  4. @MyTag(name = "吉娃娃",age = 5)
  5. public void fei(){
  6. }
  7. // 提取Annotation信息:因为Annotation接口是所有注解的父接口,
  8. //通过反射获取Annotation,将Annotation转换成具体的注解类,在调用注解类定义的方法获取元数据信息。
  9. public static void main(String[] args) throws NoSuchMethodException {
  10. Class<Dog> clazz = Dog.class;
  11. Method fei = clazz.getMethod("fei");
  12. MyTag annotation = fei.getAnnotation(MyTag.class);
  13. System.out.println(annotation.age()); // 5
  14. System.out.println(annotation.name()); // 吉娃娃
  15. }
  16. }
  • 没带成员变量的Annotation被称为标记,这种注解仅利用自身的存在与否来提供信息,如@Override等。
  • 包含成员变量的Annotation称为元数据Annotation,因为他们提供更多元数据。

    元注解

  • 元注解,修饰注解的注解

  • @Retention (保留)
    • RetentionPolicy.SOURCE 编译器使用后,直接丢弃这种策略的注解
    • RetentionPolicy.CLASS 默认值,字节码文件中存在,当程序运行时,JVM不会保留该注解
    • RetentionPolicy.RUNTIME 保存在字节码中,JVM运行时会保留,程序可以通过反射获取该注解
  • @Target 注解可以使用的位置
    • ElementType.TYPE //Class, interface (including annotation type), or enum declaration
    • ElementType.FIELD //Field declaration (includes enum constants)
    • ElementType.METHOD //方法上
    • ElementType.PARAMETER //形参
    • ElementType.CONSTRUCTOR // 构造器
    • ElementType.LOCAL_VARIABLE // 本地变量 、局部变量
    • ElementType.ANNOTATION_TYPE // 指定该该策略的Annotation只能修饰Annotation.
    • ElementType.PACKAGE // 包
    • ElementType.TYPE_PARAMETER //类型参数声明
    • ElementType.TYPE_USE // Java8新增
  • @Documented
    • 由于指定被该元注解修饰的Annotation类将被javadoc 工具提取成文档,记载生成文档时,可以看到该注解
    • 注意:定义为@Documented的注解必须设置RetentionPolicy.RUNTIME。
  • @Inherited

    • 被他修饰的注解将具有继承性。 如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解

      JDK中的基本注解(内置注解)

  • @Override 限制修饰的方法为重写方法

  • @Deprecated 标注类或方法过时
  • @SuppressWarnings 抑制编译警告
  • @Functionlnterface (java8新增):修饰函数式接口
    使用该注解修饰的接口必须是函数式接口,不然编译会出错。那么什么是函数式接口?答:如果接口中只有一个抽象方法(可以包含多个default方法或static方法),就是函数式接口。

    注解的发展

  • JDK5开始,Java增加了对元数据(MetaData)的支持,怎么支持?答:通过Annotation(注解)来实现。Annotation提供了为程序元素设置元数据的方法。元数据:描述数据的数据。

  • JDK1.7 之前访问和处理Annotation的工具统称APT(Annotation Processing Tool)(JDK1.7 后就被废除了),JDK1.7 及之后采用了JSR 269 API。
  • JDK 1.6 的tools.jar中提供了编写注解处理器的API, 同时1.6在 javax.annotation.processing and javax.lang.model包下新增了JSR269的API。 1.6两种方式并存。

    注解的处理

  • RetentionPolicy.SOURCE 表示注解在源码中,这种注解的处理:通过继承JDK 1.6提供的javax.annotation.processing.AbstractProcessor,自定义注解处理器,在使用该注解的类编译的时候执行重写的方法process 来在编译器拓展功能。

  • RetentionPolicy.CLASS 在字节码中,这种注解我猜想要用类似ASM的类库去操作拓展功能。
  • RetentionPolicy.RUNTIME 在运行的时候,通过反射获取注解信息,拓展功能。 ```java package org.apache.ibatis.annotations;

@Retention(RUNTIME) //运行时注解,通过反射处理 @Target({TYPE, METHOD, FIELD, PARAMETER}) public @interface Mapper {

}

  1. <a name="yKDfU"></a>
  2. # 提取Annotation信息
  3. - 使用Annotation修饰了类、方法、成员变量等程序元素之后,这些Annotation不会自己生效,必须由开发者通过API来提取并处理Annotation信息。
  4. - Annotation接口是所有注解的父接口。
  5. - **思路:**通过反射获取Annotation,将Annotation转换成具体的注解类,在调用注解类定义的方法获取元数据信息。
  6. **获取Annotation**
  7. - java.lang.reflect.AnnotatedElement 接口(java.lang.reflect反射包中)**代表程序中可以接受注解的程序元素。即所有可以接受注解的程序元素都会实现该接口**。而该接口就提供了获取Annotation的方法,它的所有实现类也便拥有了这些方法。
  8. - 常见的实现类:
  9. - Class:类定义。
  10. - Constructor:构造器定义
  11. - Field:类的成员变量定义
  12. - Method:类的方法定义。
  13. - Package:类的包定义。
  14. - 由此可见,AnnotatedElement接口的实现类都是一些反射技术设计到的类,所以访问Annotation信息也是通过反射技术来实现的。
  15. - java.lang.reflect包下还包含实现反射功能的工具类,java5开始,java.lang.reflect包提供的反射API增加了读取允许Annotation的能力。但是**,**_**只有定义Annotation时使用了@Rentention(RetentionPolicy.RUNTIME)修饰,该Annotation才会在运行时可见,JVM才会在装载.class文件时读取保存在class文件中的Annotation**_。
  16. - AnnotatedElement接口获取Annotation信息的方法:
  17. - <T extends Annotation> T getAnnotation(Class<T> annotationClass):返回修饰该程序元素的指定类型的注解,不存在则返回 null。
  18. - <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass):返回直接修饰该程序元素的指定类型的注解,不存在则返回 null。 (java8新增)
  19. - Annotation[] getAnnotations():返回此元素上存在的所有注解。
  20. - Annotation[] getDeclaredAnnotations():返回**直接**存在于此元素上的所有注解。
  21. - boolean isAnnotationPresent (Class< ? extends Annotation> annotationClass):如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。
  22. - java8新增了重复注解功能,所以下面两个方法在java8之后才有:
  23. - <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass):返回修饰该程序元素的指定类型的多个注解,不存在则返回 null。
  24. - <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass):返回直接修饰该程序元素的指定类型的多个注解,不存在则返回 null。
  25. <a name="rcY2Q"></a>
  26. # Java8 新增的重复注解
  27. 在Java8以前,同一个程序元素只能使用一个相同类型的Annotation。如下代码是错误的。
  28. ```java
  29. //代码错误,不可以使用相同注解在一个程序元素上。
  30. @MyTag(name="liang")
  31. @MyTag(name="huan")
  32. public void info(){
  33. }

Java8 之前实现思路

要想达到使用多个注解的目的,可以使用注解”容器“:其实就是新定义一个注解DupMyTag ,让这个DupMyTag 注解的成员变量value的类型为注解MyTag数组。这样就可以通过注解DupMyTag 使用多个注解MyTag了。换个思路实现,只是书写形式不一样而已。

  1. package cn.java.money.annotations.demo3;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. @Retention(RetentionPolicy.RUNTIME)
  7. @Target(ElementType.METHOD)
  8. public @interface MyTag {
  9. String name();
  10. int age() default 32;
  11. }
  1. package cn.java.money.annotations.demo3;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. @Retention(RetentionPolicy.RUNTIME)
  7. @Target(value = ElementType.METHOD)
  8. public @interface DupMyTag {
  9. //成员变量为MyTag数组类型
  10. MyTag[] value();
  11. }
  1. package cn.java.money.annotations.demo3;
  2. import java.lang.reflect.Method;
  3. public class Dog {
  4. //在同一个程序元素上使用了多个相同的注解MyTag
  5. @DupMyTag ({ @MyTag(name="liang"), @MyTag(name="huan",age=18)})
  6. public void fei(){
  7. }
  8. public static void main(String[] args) throws NoSuchMethodException {
  9. Class<Dog> clazz = Dog.class;
  10. Method fei = clazz.getMethod("fei");
  11. DupMyTag annotation = fei.getAnnotation(DupMyTag.class);
  12. MyTag[] myTags = annotation.value();
  13. for (MyTag myTag : myTags) {
  14. System.out.println(myTag.name());
  15. System.out.println(myTag.age());
  16. }
  17. }
  18. }

结论:通过新定义一个容器注解,来实现使用多个相同注解的目的,只是书写形式不能达到期待效果而已,要想书写形式能达到期待效果需要使用Java8之后的@Repeatable元注解。
注:”容器“注解的保留期Retention必须比它所包含注解的保留期更长,否则编译报错

Java8之后

@Repeatable中的注解【DupMyTag.class】,必须要提供 value方法,并且返回置为当前被修饰的注解【@MyTag】为类型的数组

  1. //@Repeatable中的注解【DupMyTag.class】,必须要提供 value方法,
  2. // 并且返回置为当前被修饰的注解【@MyTag】为类型的数组
  3. @Repeatable(DupMyTag.class)
  4. @Retention(RetentionPolicy.RUNTIME)
  5. @Target(ElementType.METHOD)
  6. public @interface MyTag{
  7. //定义两个成员变量,以方法的形式定义
  8. String name();
  9. int age() default 32;
  10. }
  1. package cn.java.money.annotations.demo4;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. @Retention(RetentionPolicy.RUNTIME)
  7. @Target(value = ElementType.METHOD)
  8. public @interface DupMyTag {
  9. MyTag[] value();
  10. }
  1. package cn.java.money.annotations.demo4;
  2. import java.lang.reflect.Method;
  3. public class Dog {
  4. //在同一个程序元素上使用了多个相同的注解 MyTag
  5. //@DupMyTag({@MyTag(name = "liang"), @MyTag(name = "huan", age = 18)}) //这样也可以
  6. @MyTag(name = "liang")
  7. @MyTag(name = "huan", age = 18) //这里体现了可重复注解 用了两次 @MyTag
  8. public void fei() {
  9. }
  10. public static void main(String[] args) throws NoSuchMethodException {
  11. Class<Dog> clazz = Dog.class;
  12. Method fei = clazz.getMethod("fei");
  13. // getAnnotationsByType为了处理重复注解
  14. MyTag[] myTags = fei.getAnnotationsByType(MyTag.class);
  15. for (MyTag myTag : myTags) {
  16. System.out.println(myTag.name());
  17. System.out.println(myTag.age());
  18. }
  19. }
  20. }

Java8 新增的Type Annotation注解

  • 目的:以前的注解只能用在包、类、构造器、方法、成员变量、参数、局部变量。 如果想在:创建对象(通过new创建)、类型转换、使用implements实现接口、使用throws声明抛出异常的位置使用注解就不行了。而Type Annotation注解就为了这个而来。
  • 抽象表述: java为ElementType枚举增加了TYPE_PARAMETER、TYPE_USE两个枚举值。@Target(TYPE_USE)修饰的注解称为Type Annotation(类型注解),Type Annotation可用在任何用到类型的地方。


  1. package cn.java.money.annotations.demo5;
  2. import java.lang.annotation.*;
  3. //注意这里使用的是:ElementType.TYPE_USE
  4. @Target(ElementType.TYPE_USE)
  5. @Retention(RetentionPolicy.RUNTIME)
  6. @Documented
  7. public @interface NotNull {
  8. String value() default "";
  9. }
  1. package cn.java.money.annotations.demo5;
  2. import java.io.Serializable;
  3. //implements实现接口中使用 Type Annotation
  4. public class Dog implements @NotNull(value = "Serializablexxxx") Serializable {
  5. //泛型中使用Type Annotation 、 抛出异常中使用Type Annotation
  6. // public void foo(List<@NotNull String> list) throws @NotNull(value = "ClassNotFoundExceptionxxx") ClassNotFoundException {
  7. public void foo() throws @NotNull(value = "ClassNotFoundExceptionxxxx") ClassNotFoundException {
  8. //创建对象中使用Type Annotation
  9. Object obj = new @NotNull String("annotation.Test");
  10. //强制类型转换中使用Type Annotation
  11. String str = (@NotNull String) obj;
  12. }
  13. }
  1. package cn.java.money.annotations.demo5;
  2. import java.lang.annotation.Annotation;
  3. import java.lang.reflect.AnnotatedType;
  4. import java.lang.reflect.Method;
  5. import java.lang.reflect.Type;
  6. /**
  7. * java8提供AnnotatedType接口,该接口用来代表被注解修饰的类型。该接口继承AnnotatedElement接口。
  8. * 同时多了一个public Type getType()方法,用于返回注解修饰的类型。
  9. */
  10. public class GetTypeAnnotation {
  11. public static void main(String[] args) {
  12. try {
  13. Class clazz = Class.forName("cn.java.money.annotations.demo5.Dog");
  14. //获取类继承的、带注解的接口
  15. AnnotatedType[] aInterfaces = clazz.getAnnotatedInterfaces();
  16. print(aInterfaces);
  17. Method method = clazz.getMethod("foo");
  18. //获取方法上抛出的带注解的异常
  19. AnnotatedType[] aExceptions = method.getAnnotatedExceptionTypes();
  20. print(aExceptions);
  21. } catch (NoSuchMethodException e) {
  22. e.printStackTrace();
  23. } catch (SecurityException e) {
  24. e.printStackTrace();
  25. } catch (ClassNotFoundException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. public static void print(AnnotatedType[] array) {
  30. for (AnnotatedType at : array) {
  31. Type type = at.getType();//获取基础类型
  32. Annotation[] ans = at.getAnnotations();//获取注解
  33. //打印类型
  34. System.out.println(type);
  35. //打印注解
  36. for (Annotation an : ans) {
  37. System.out.println(an);
  38. }
  39. System.out.println("------------");
  40. }
  41. }
  42. }

interface java.io.Serializable

@cn.java.money.annotations.demo5.NotNull(value=Serializablexxxx)

class java.lang.ClassNotFoundException

@cn.java.money.annotations.demo5.NotNull(value=ClassNotFoundExceptionxxxx)

编译时处理Annotation

需求

  • 有过Hibernate开发经验的朋友可能知道每写一个Java文件,还必须额外地维护一个Hibernate映射文件(一个名为.hbm.xml的文件,当然可以有一些工具可以自动生成)下面将使用Annotation来简化这步操作。思路:自定义修饰类的注解,在实体类上使用注解,编写注解处理器:根据源文件中的类上的注解,生成.hbm.xml文件,使用java提供的编译命令javac执行注解处理器。关键:编写注解处理器。

    可用api

  • 我们知道前面的注解处理器处理的都是@Retention(RetentionPolicy.RUNTIME)的注解,使用的是反射技术。而生成的*hbm.xml文件是需要在编译阶段完成。为此java在java7之前提供了apt工具及API,在java7及之后提供了JSR269 API。

    apt和jsr269的作用

  • APT(Annotation processing tool) 是一种处理注释的工具,它对源代码文件进行检测,并找出源文件中所包含的Annotation信息,然后针对Annotation信息进行额外的处理。

  • APT处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件.使用APT主要的目的是简化开发者的工作量。
  • 因为APT可以编译程序源代码的同时,生成一些附属文件(比如源文件、类文件、程序发布描述文件等),这些附属文件的内容也都是与源代码相关的,换句话说,使用APT可以代替传统的对代码信息和附属文件的维护工作。
  • APT的相关api都在com.sun.mirror 包下,在jdk7及之后,apt的相关api就被废除了,代替的是JSR269。JSR269API文档下载。JSR269的api在 javax.annotation.processing and javax.lang.model包下。
    所以以后开发注解处理器使用jsr269提供的api就可以了。

    使用JSR269实现

  • 运行环境jdk1.8

  • Java提供的javac.exe工具有一个-processor选项,该选项可指定一个Annotation处理器,如果在编译java源文件的时候通过该选项指定了Annotation处理器,那么这个Annotation处理器,将会在编译时提取并处理Java源文件中的Annotation。
  • 每个Annotation处理器都需要实现javax.annotation.processing包下的Processor接口。不过实现该接口必须实现它里面所有方法,因此通常采用继承AbstractProcessor的方式来实现Annotation处理器,一个Annotation处理器可以处理一种或多种Annotation类型。
  • 之前的错误认识:之前以为-processor选项需要指定注解处理器是一个*.java文件,其实是一个.class文件,既然是.class文件,那么肯定是编译过后的,所以需要单独写一个处理器程序annotation-processor,打成一个jar包,然后在使用注解的程序annotation中加入注解处理器依赖包annotation-processor.jar,在编译的时候指定处理器类即可。下面我会分别演示通过javac 命令和maven命令如何进行操作。
  • 下面的项目会使用maven来构建,如果不是使用maven也可以,因为我也会演示如何通过javac 命令来执行注解处理器。