- 注解(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 0x2601
public 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()); // 5
System.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) //这里体现了可重复注解 用了两次 @MyTag
public 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)
@Documented
public @interface NotNull {
String value() default "";
}
package cn.java.money.annotations.demo5;
import java.io.Serializable;
//implements实现接口中使用 Type Annotation
public 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 Annotation
Object obj = new @NotNull String("annotation.Test");
//强制类型转换中使用Type Annotation
String 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 命令来执行注解处理器。