- 注解(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 {
}
字节码```java// class version 52.0 (52)// access flags 0x2601public abstract @interface cn/java/money/annotations/demo1/MyAnnotation implements java/lang/annotation/Annotation {// compiled from: MyAnnotation.java@Ljava/lang/annotation/Documented;()@Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.CLASS)@Ljava/lang/annotation/Target;(value={Ljava/lang/annotation/ElementType;.TYPE})}
- 注解本质上实现的是 java.lang.annotation.Annotation 接口, 也就是Annotation接口是所有注解的父接口。
- 接口中的方法 
- Class<? extends Annotation> annotationType();
 - boolean equals(Object obj);
 - int hashCode();
 - String toString();
 
 
 - 接口中的方法 
 
带成员变量的注解
以无形参的方法形式来声明Annotation的成员变量,方法名和返回值定义了成员变量名称和类型。使用default关键字设置初始值。没设置初始值的变量则使用时必须提供,有初始值的变量可以设置也可以不设置。
package cn.java.money.annotations.demo2;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;//定义带成员变量注解 MyTag@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MyTag{//定义两个成员变量,以方法的形式定义//方法名和返回值定义了成员变量名称和类型String name();int age() default 32;}
package cn.java.money.annotations.demo2;import java.lang.reflect.Method;public class Dog{@MyTag(name = "吉娃娃",age = 5)public void fei(){}// 提取Annotation信息:因为Annotation接口是所有注解的父接口,//通过反射获取Annotation,将Annotation转换成具体的注解类,在调用注解类定义的方法获取元数据信息。public static void main(String[] args) throws NoSuchMethodException {Class<Dog> clazz = Dog.class;Method fei = clazz.getMethod("fei");MyTag annotation = fei.getAnnotation(MyTag.class);System.out.println(annotation.age()); // 5System.out.println(annotation.name()); // 吉娃娃}}
- 没带成员变量的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
@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 {
}
<a name="yKDfU"></a># 提取Annotation信息- 使用Annotation修饰了类、方法、成员变量等程序元素之后,这些Annotation不会自己生效,必须由开发者通过API来提取并处理Annotation信息。- Annotation接口是所有注解的父接口。- **思路:**通过反射获取Annotation,将Annotation转换成具体的注解类,在调用注解类定义的方法获取元数据信息。**获取Annotation**- java.lang.reflect.AnnotatedElement 接口(java.lang.reflect反射包中)**代表程序中可以接受注解的程序元素。即所有可以接受注解的程序元素都会实现该接口**。而该接口就提供了获取Annotation的方法,它的所有实现类也便拥有了这些方法。- 常见的实现类:- Class:类定义。- Constructor:构造器定义- Field:类的成员变量定义- Method:类的方法定义。- Package:类的包定义。- 由此可见,AnnotatedElement接口的实现类都是一些反射技术设计到的类,所以访问Annotation信息也是通过反射技术来实现的。- java.lang.reflect包下还包含实现反射功能的工具类,java5开始,java.lang.reflect包提供的反射API增加了读取允许Annotation的能力。但是**,**_**只有定义Annotation时使用了@Rentention(RetentionPolicy.RUNTIME)修饰,该Annotation才会在运行时可见,JVM才会在装载.class文件时读取保存在class文件中的Annotation**_。- AnnotatedElement接口获取Annotation信息的方法:- <T extends Annotation> T getAnnotation(Class<T> annotationClass):返回修饰该程序元素的指定类型的注解,不存在则返回 null。- <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass):返回直接修饰该程序元素的指定类型的注解,不存在则返回 null。 (java8新增)- Annotation[] getAnnotations():返回此元素上存在的所有注解。- Annotation[] getDeclaredAnnotations():返回**直接**存在于此元素上的所有注解。- boolean isAnnotationPresent (Class< ? extends Annotation> annotationClass):如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。- java8新增了重复注解功能,所以下面两个方法在java8之后才有:- <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass):返回修饰该程序元素的指定类型的多个注解,不存在则返回 null。- <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass):返回直接修饰该程序元素的指定类型的多个注解,不存在则返回 null。<a name="rcY2Q"></a># Java8 新增的重复注解在Java8以前,同一个程序元素只能使用一个相同类型的Annotation。如下代码是错误的。```java//代码错误,不可以使用相同注解在一个程序元素上。@MyTag(name="liang")@MyTag(name="huan")public void info(){}
Java8 之前实现思路
要想达到使用多个注解的目的,可以使用注解”容器“:其实就是新定义一个注解DupMyTag ,让这个DupMyTag 注解的成员变量value的类型为注解MyTag数组。这样就可以通过注解DupMyTag 使用多个注解MyTag了。换个思路实现,只是书写形式不一样而已。
package cn.java.money.annotations.demo3;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MyTag {String name();int age() default 32;}
package cn.java.money.annotations.demo3;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)@Target(value = ElementType.METHOD)public @interface DupMyTag {//成员变量为MyTag数组类型MyTag[] value();}
package cn.java.money.annotations.demo3;import java.lang.reflect.Method;public class Dog {//在同一个程序元素上使用了多个相同的注解MyTag@DupMyTag ({ @MyTag(name="liang"), @MyTag(name="huan",age=18)})public void fei(){}public static void main(String[] args) throws NoSuchMethodException {Class<Dog> clazz = Dog.class;Method fei = clazz.getMethod("fei");DupMyTag annotation = fei.getAnnotation(DupMyTag.class);MyTag[] myTags = annotation.value();for (MyTag myTag : myTags) {System.out.println(myTag.name());System.out.println(myTag.age());}}}
结论:通过新定义一个容器注解,来实现使用多个相同注解的目的,只是书写形式不能达到期待效果而已,要想书写形式能达到期待效果需要使用Java8之后的@Repeatable元注解。
注:”容器“注解的保留期Retention必须比它所包含注解的保留期更长,否则编译报错
Java8之后
@Repeatable中的注解【DupMyTag.class】,必须要提供 value方法,并且返回置为当前被修饰的注解【@MyTag】为类型的数组
//@Repeatable中的注解【DupMyTag.class】,必须要提供 value方法,// 并且返回置为当前被修饰的注解【@MyTag】为类型的数组@Repeatable(DupMyTag.class)@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MyTag{//定义两个成员变量,以方法的形式定义String name();int age() default 32;}
package cn.java.money.annotations.demo4;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)@Target(value = ElementType.METHOD)public @interface DupMyTag {MyTag[] value();}
package cn.java.money.annotations.demo4;import java.lang.reflect.Method;public class Dog {//在同一个程序元素上使用了多个相同的注解 MyTag//@DupMyTag({@MyTag(name = "liang"), @MyTag(name = "huan", age = 18)}) //这样也可以@MyTag(name = "liang")@MyTag(name = "huan", age = 18) //这里体现了可重复注解 用了两次 @MyTagpublic void fei() {}public static void main(String[] args) throws NoSuchMethodException {Class<Dog> clazz = Dog.class;Method fei = clazz.getMethod("fei");// getAnnotationsByType为了处理重复注解MyTag[] myTags = fei.getAnnotationsByType(MyTag.class);for (MyTag myTag : myTags) {System.out.println(myTag.name());System.out.println(myTag.age());}}}
Java8 新增的Type Annotation注解
- 目的:以前的注解只能用在包、类、构造器、方法、成员变量、参数、局部变量。 如果想在:创建对象(通过new创建)、类型转换、使用implements实现接口、使用throws声明抛出异常的位置使用注解就不行了。而Type Annotation注解就为了这个而来。
 - 抽象表述: java为ElementType枚举增加了TYPE_PARAMETER、TYPE_USE两个枚举值。@Target(TYPE_USE)修饰的注解称为Type Annotation(类型注解),Type Annotation可用在任何用到类型的地方。
 
package cn.java.money.annotations.demo5;import java.lang.annotation.*;//注意这里使用的是:ElementType.TYPE_USE@Target(ElementType.TYPE_USE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface NotNull {String value() default "";}
package cn.java.money.annotations.demo5;import java.io.Serializable;//implements实现接口中使用 Type Annotationpublic class Dog implements @NotNull(value = "Serializablexxxx") Serializable {//泛型中使用Type Annotation 、 抛出异常中使用Type Annotation// public void foo(List<@NotNull String> list) throws @NotNull(value = "ClassNotFoundExceptionxxx") ClassNotFoundException {public void foo() throws @NotNull(value = "ClassNotFoundExceptionxxxx") ClassNotFoundException {//创建对象中使用Type AnnotationObject obj = new @NotNull String("annotation.Test");//强制类型转换中使用Type AnnotationString str = (@NotNull String) obj;}}
package cn.java.money.annotations.demo5;import java.lang.annotation.Annotation;import java.lang.reflect.AnnotatedType;import java.lang.reflect.Method;import java.lang.reflect.Type;/*** java8提供AnnotatedType接口,该接口用来代表被注解修饰的类型。该接口继承AnnotatedElement接口。* 同时多了一个public Type getType()方法,用于返回注解修饰的类型。*/public class GetTypeAnnotation {public static void main(String[] args) {try {Class clazz = Class.forName("cn.java.money.annotations.demo5.Dog");//获取类继承的、带注解的接口AnnotatedType[] aInterfaces = clazz.getAnnotatedInterfaces();print(aInterfaces);Method method = clazz.getMethod("foo");//获取方法上抛出的带注解的异常AnnotatedType[] aExceptions = method.getAnnotatedExceptionTypes();print(aExceptions);} catch (NoSuchMethodException e) {e.printStackTrace();} catch (SecurityException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}public static void print(AnnotatedType[] array) {for (AnnotatedType at : array) {Type type = at.getType();//获取基础类型Annotation[] ans = at.getAnnotations();//获取注解//打印类型System.out.println(type);//打印注解for (Annotation an : ans) {System.out.println(an);}System.out.println("------------");}}}
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 命令来执行注解处理器。
 
