1.介绍
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。
内置的注解
Java 定义了一套注解,共有7个,3个在 java.lang 中,剩下4个在 java.lang.annotation 中。
作用在代码(java.lang) 中注解是
@Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
@SuppressWarnings - 指示编译器去忽略注解中声明的警告。
作用在其他(java.lang.annotation)注解的注解(或者说元注解)是:
@Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
@Documented - 标记这些注解是否包含在用户文档中。
@Target - 标记这个注解应该是哪种 Java 成员。
@Inherited - 标记这个注解是继承于哪个注解类(默认注解并没有继承于任何子类)
从Java 7开始,额外添加了3个注解:
@SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
@FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
@Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
2.Annotation定义
Annotation
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
每1个Annotation都与1个RetentionPolicy关联,并且与1~n个ElementType关联
定义
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name();
boolean boo() default false;
Class<?> cla() default Void.class;
String[] arr();
}
@interface
使用 @interface 定义注解时,意味着它继承了 java.lang.annotation.Annotation 接口,即该注解就是一个Annotation。定义 Annotation 时,@interface 是必须的
注解的本质就是一个继承了 Annotation 接口的接口
- 注解方法不能有参数;语法是方法,使用是属性。
- 注解方法的返回类型局限于基本数据类型,字符串,枚举,注解,Class或以上类型构成的数组。
- 注解方法可以包含默认值,使用时,无默认值的元素必须传值。
- 注解可以包含与其绑定的元注解,元注解为注解提供信息。
- 注解必须是public修饰,无修饰符默认public
赋值:
数组属性:就有一个元素,可以省略大括号;多个值必须用大括号括起来,数组若没有值用空大括号
value属性:如果给value属性赋值可以省略value=,如果给多个属性赋值则不能省略
3.元注解
@Documented
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
类和方法的 Annotation 在缺省情况下是不出现在 javadoc 中的。如果使用 @Documented 修饰该 Annotation,则表示它可以出现在 javadoc 中。
定义 Annotation 时,@Documented 可有可无;若没有定义,则 Annotation 不会出现在 javadoc 中
例如:
/**
* 自定义注解
*/
@Documented
public @interface MyAnnotation {
// 名称
String name();
// 作者
String author();
}
/**
* 文档类
*/
@MyAnnotation(name = "document", author = "shoudongma")
public class Document {
}
@Target
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
指定 Annotation 的类型(即ElementType )属性。
@Target(ElementType.TYPE) 的意思就是指定该 Annotation 的类型是 ElementType.TYPE。这就意味着,MyAnnotation是来修饰"类、接口(包括注释类型)或枚举声明"的注解。
定义 Annotation 时,@Target 可有可无。若有 @Target,则该 Annotation 只能用于它所指定的地方;如果一个注解没有指定@Target注解,则此注解可以用于除了TYPE_PARAMETER和TYPE_USE以外的任何地方。
ElementType
package java.lang.annotation;
public enum ElementType {
TYPE, /* 类、接口(包括注释类型)或枚举声明 */
FIELD, /* 字段声明(包括枚举常量) */
METHOD, /* 方法声明 */
PARAMETER, /* 参数声明 */
CONSTRUCTOR, /* 构造方法声明 */
LOCAL_VARIABLE, /* 局部变量声明 */
ANNOTATION_TYPE, /* 注释类型声明 */
PACKAGE, /* 包声明 */
TYPE_PARAMETER, /* @since 1.8类参数声明,可以应用于类的泛型声明之处 */
TYPE_USE /* @since 1.8类中任意地方声明*/
}
// 定义三个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TypeAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_PARAMETER)
public @interface TypeParameterAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
public @interface TypeUseAnnotation {
}
校验使用的场景
public class Document<@TypeParameterAnnotation T> {
// 编译不通过,类型注解@TypeAnnotation不能声明方法、参数
// public @TypeAnnotation T test(@TypeAnnotation T t) {
// return t;
// }
// 编译不通过,类型参数注解@TypeParameterAnnotation不能声明方法、参数
// public @TypeParameterAnnotation T test(@TypeParameterAnnotation T t) {
// return t;
// }
// 通过
public @TypeUseAnnotation T test(@TypeUseAnnotation T t) {
return t;
}
}
@TypeUseAnnotation
class A<@TypeUseAnnotation T>{}
@Retention
翻译:美 [rɪˈtenʃn] n.保留; 保持; 维持
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
RetentionPolicy是Annotation的策略属性,定义了该Annotation被保留的时间长短:
1.某些Annotation仅出现在源代码中,而被编译器丢弃;
2.另一些却被编译在class文件中,注解保留在class文件中,在加载到JVM虚拟机时丢弃,这是默认行为,所以没有用Retention注解的注解,都会采用这种策略
3.而另一些在class被装载时将被读取,注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
Retention meta-annotation类型有唯一的value作为成员
@Retention(RetentionPolicy.RUNTIME) -- z这就意味着,编译器会将Deprecated 的信息保留在 .class 文件中,并且能被虚拟机读取。
RetentionPolicy
package java.lang.annotation;
public enum RetentionPolicy {
SOURCE, /* 注解只保留在源文件,当Java文件被编译器编译成.class文件的时候,注解被遗弃 */
CLASS, /* 编译器将Annotation存储于类对应的.class文件中。但jvm加载.class文件时候被遗弃,这是默认的生命周期 */
RUNTIME /* 注解不仅被保存到.class文件中,jvm加载.class文件之后,仍然存在,因此它们主要用于反射场景,可以通过getAnnotation方法获取 */
}
这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码
怎么选择合适生命周期
一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解,比如@Deprecated使用RUNTIME注解
如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;
如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,使用SOURCE 注解
例子:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Country {
// 国家名称
String country();
}
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Province {
// 省份名
String province();
// 所属国家
String country();
}
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface City {
// 城市名
String city();
// 所属省份
String province();
}
@Country(country = "中国")
@Province(country = "中国", province = "四川")
@City(province = "四川", city = "成都")
public class TestMain {
public static void main(String[] args) {
Annotation[] annotations = TestMain.class.getAnnotations();
System.out.println("获取能保留到运行时的注解:");
for (Annotation annotation :annotations){
System.out.println(annotation.toString()); // 输出一个:@com.xx.Country(country=中国)
}
}
}
@Inherited
美 [ɪnˈherɪtɪd] v.继承;遗传;接替
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
含义是,它所标注的Annotation将具有继承性。
假设,我们定义了某个 Annotaion,它的名称是 MyAnnotation,并且 MyAnnotation 被标注为 @Inherited。现在,某个类 Base 使用了@MyAnnotation;现在,Sub 继承了 Base,由于 MyAnnotation 是 @Inherited的(具有继承性),所以,Sub 也 "具有了注解 MyAnnotation"。
@Repeatable
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
/**
* Indicates the <em>containing annotation type</em> for the
* repeatable annotation type.
* @return the containing annotation type
*/
Class<? extends Annotation> value();
}
标识注解是可重复使用的
// 注解1
public @interface MyAnnotations {
MyAnnotation[] value();
}
// 注解2
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
int value();
}
// 有@Repeatable使用
@MyAnnotation(1)
@MyAnnotation(2)
@MyAnnotation(3)
public class MyTest { ... }
// 没有@Repeatable()时的使用,@MyAnnotation去掉@Repeatable元注解
@MyAnnotations({
@MyAnnotation(1),
@MyAnnotation(2),
@MyAnnotation(3)})
public class MyTest { ... }
Spring的@ComponentScan注解也用到这个元注解
4.java.lang下注解
@SuppressWarnings
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
@SuppressWarnings注解主要用在取消一些编译器产生的警告对代码工具左侧行列提示,但这种警告可以通过注释类型声明来取消
String[] value(); 意味着,SuppressWarnings 能指定参数
关键字 | 用途 | 解释 |
---|---|---|
all | to suppress all warnings | 抑制所有警告 |
boxing | to suppress warnings relative to boxing/unboxing operations | 抑制装箱、拆箱操作时候的警告 |
cast | to suppress warnings relative to cast operations | 抑制映射相关的警告 |
dep-ann | to suppress warnings relative to deprecated annotation | 抑制启用注释的警告 |
deprecation | to suppress warnings relative to deprecation | 抑制过期方法警告 |
fallthrough | to suppress warnings relative to missing breaks in switch statements | 抑制在switch中缺失break的警告 |
finally | to suppress warnings relative to finally block that don’t return | 抑制finally模块没有返回的警告 |
hiding | to suppress warnings relative to locals that hide variable | 抑制隐藏变量的警告 |
incomplete-switch | to suppress warnings relative to missing entries in a switch statement (enum case) | 忽略没有完整的switch语句 |
nls | to suppress warnings relative to non-nls string literals | 忽略非nls格式的字符 |
null | to suppress warnings relative to null analysis | 忽略对null的操作 |
rawtypes | to suppress warnings relative to un-specific types when using generics on class params | 使用generics时忽略没有指定相应的类型 |
restriction | to suppress warnings relative to usage of discouraged or forbidden references | 取消使用不鼓励或禁止的引用的警告 |
serial | to suppress warnings relative to missing serialVersionUID field for a serializable class | 忽略在serializable类中没有声明serialVersionUID变量 |
static-access | to suppress warnings relative to incorrect static access | 抑制不正确的静态访问方式警告 |
synthetic-access | to suppress warnings relative to unoptimized access from inner classes | 抑制子类没有按最优方法访问内部类的警告 |
unchecked | to suppress warnings relative to unchecked operations | 抑制没有进行类型检查操作的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型 |
unqualified-field-access | to suppress warnings relative to field access unqualified | 抑制没有权限访问的域的警告 |
unused | to suppress warnings relative to unused code | 抑制没被使用过的代码的警告 |
5.注解处理类
注解处理器类库(java.lang.reflect.AnnotatedElement)
AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:
- <T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
- Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
- boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
- Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
例子
针对一些标志位,我们必须保证其 数值只是 Integer类型的 0 或者 1,这时需要我们自定义注解来进行实现
@Documented
// 标明这个校验注解是使用哪个校验器进行校验的,在这里指定或者在初始化的时候指定
@Constraint(validatedBy = { ListValConstraintValidator.class})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) // 作用位置
@Retention(RUNTIME) // 运行时机
public @interface ListValue {
//在 jsr303 中一个注解必须有下面三个属性
String message() default "{com.demrystv.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default { };
}
package com.demrystv.common.valid;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
/**
*
* 自定义校验器
* 首先必须实现 ConstraintValidator 接口, 其中第一个参数是 自定义校验注解,第二个参数是校验的类型
*/
public class ListValConstraintValidator implements ConstraintValidator<ListValue, Integer> {
Set<Integer> set = new HashSet<>();
// 初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals(); // vals是注解中的设置的固定参数
for (int val : vals) {
set.add(val);
}
}
/**
* 进行校验
* @param value 提交的需要被校验的值
* @param context 上下文环境
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
常用在aop切面的时候指定注解切面