Java注解(Annotation)

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 {
}
image.png

@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切面的时候指定注解切面

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容