注解

注解,其实是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,可以在不改变原有逻辑的情况下,在源文件中嵌入补充一些信息。

Annotation 提供了一种为程序元素设置元数据的方法,Annontation 就像修饰符一样可以修饰包、类、构造器、成员变量的声明,这些信息被存储在 Annotation 的 "name=value" 对中。

Annotation 是一个接口,程序可以通过反射来获取指定程序元素的 Annotation 对象,然后通过 Annotation 对象来取得注解里的元数据。

基本 Annotation

  • @Override
  • @Deprecated
  • @SuppressWarnings
  • @FunctionalInterface:用来指定某个接口必须是函数式接口

Meta Annotation

java.lang.annotation 包下提供了 6 个元 Annotation ,其中 5 个用于修饰其他的 Annotation 定义。 其中 @Repeatable 专门用于定义 Java8 新增的重复注解。

@Retention

用于指定被修饰的 Annotation 可以保留多长时间。

@Retention 包含一个 RetentionPolicy 类型的 value 成员变量,使用 @Retention 时必须为该 value 变量指定值。

value 成员变量的只能是如下三个:

  • RetentionPolicy.CLASS:编译器将 Annotation 记录在 class 文件中。当运行程序时,JVM 不可获取 Annotation 信息。这是默认值

  • RetentionPolicy.RUNTIME:编译器将 Annotation 记录在 class 文件中。程序可以通过反射获取该 Annotation 信息

  • RetentionPolicy.SOURCE:Annotation 只保留在源代码中,编译器直接丢弃这种 Annotation

// 定义下面的 Testable Annotation 保留到运行时
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Testable()

@Target

用于指定被修饰的 Annotation 能用于修饰哪些程序单元,比如构造器、成员变量、局部变量、方法等。

@Target 也包含一个名为 value 的成员变量。具体的取值请查看 API 文档。

// 下面代码指定 @ActionListenFor Annotation 只能修饰方法
@Target(value = ElementType.Method)
public @interface ActionListenFor{}

@Documented

@Documented 用于指定被该 Meta Annotation 修饰的 Annotation 将被 javadoc 工具提取成文档。

@Inherited

@Inherited 指定被它修饰的 Annotation 将具有继承性。如果某个类使用了 @XXX Annotation ,(且定义该 Annotation 时使用了 @Inherited 修饰)修饰,那么这个类的子类将自动被 @XXX 修饰。

自定义 Annotation

关键字

使用 @interface 关键字。

// 定义一个简单的 Annotation 类型
public @interface Test {}

默认情况下,Annotation 可以用于修饰任何程序元素,包括类、接口、方法等。

public class MyClass {
    // 使用 @Test Annotation 修饰方法
    @Test
    public void info() {}
}

成员变量

Annotation 可以携带成员变量。

成员变量在 Annotation 定义中以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型。

public @interface MyTag {
    // 定义两个带成员变量的 Annotation
    String name();
    int age();
}
public class Test {
    @MyTag(name="xx", age=6)
    public void info() {}
}

定义 Annotation 的成员变量时为其指定默认值,使用 default 关键字。

这样在使用该 Annotation 时可以不为这些成员变量指定值,而是直接使用默认值。

public @interface MyTag {
    // 定义两个带成员变量的 Annotation
    String name() default "Tianny";
    int age() default 32;
}

提取 Annotation 信息

原理

Java 提供 Annotation 接口来表示程序元素前的注解,该接口是所有注解的父接口。

java.lang.reflect 包下的 AnnotatedElement 接口,表示可以接受注解的程序元素。

AnnotatedElement 接口主要有如下几个实现类:

  • Class: 类定义
  • Constructor:构造器定义
  • Field:成员变量定义
  • Method:方法定义
  • Package:包定义

AnnotatedElement 接口是所有程序元素的父接口,所以通过反射获取某个类的 AnnotatedElement 对象(如 Class、Method、Constructor 等)之后,就可以调用该对象的指定方法获取 Annotation 信息。

java.lang.reflect 包提供的反射 API 增加了读取运行时 Annotation 的能力。前提是只有当定义 Annotation 时使用了 @Retention(RetentionPolicy.RUNTIME) 修饰,该 Annotation 才会在运行时可见,JVM 才会在加载 class 文件时读取保存在 class 文件中的 Annotation。

提取

获取某个注解里的元数据,可以将注解强制转换成所需的注解类型,然后通过注解对象的抽象方法来访问这些元数据。

// 获取 tt 对象的 info 方法所包含的所有注解
Annotation[] annotation = tt.getClass().getMethod("info").getAnnotations();
// 遍历每个注解对象
for (Annotation tag : annotation) {
    // 如果 tag 注解是 MyTag 类型
    if (tag instanceof MyTag) {
        System.out.prinln("Tag is " + tag);
        // 将 tag 强制类型转换为 MyTag
        System.out.prinln("tag.name(): " + ((MyTag)tag).name());
        System.out.prinln("tag.age(): " + ((MyTag)tag).age());
    }
}

使用 Annotation

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
// 定义一个注解,不包含任何成员变量,即不可传入元数据
public @interface Testable {
}

public class MyTest {
    // 使用 @Testable 注解指定该方法是可测试的
    @Testable
    public static void m1() {}

    @Testable
    public static void m2() {}

    @Testable
    public static void m3() {
        throw new RuntimeException("异常");
    }

    public static void m4() {}
}
import java.lang.reflect.*;

public class ProcessorTest {
    public static void process(String clazz) throws ClassNotFoundException {
        int passwd = 0;
        int failed = 0;

        // 遍历 clazz 类里对应的所有方法
        for (Method m : Class.forName(clazz).getMethods()) {
            // 如果该方法使用了 @Testable 修饰
            if (m.isAnnotationPresent(Testable.class)) {
                try {
                    // 调用 m 方法
                    m.invoke(null);
                    // 测试成功,passwd 计数 + 1
                    passwd++;
                } catch (Exception e) {
                    System.out.println("方法" + m + "运行失败,异常: " + e.getCause());
                    failed++;
                }
            }
        }
        System.out.println("共运行了: " + (passwd + failed) + "个方法,其中: \n" + "失败了: " + failed + "个, \n" + "成功了: " + passwd + "个");
    }
}
public class RunTests {
    public static void main(String[] args) throws Exception {
        ProcessorTest.process("MyTest");
    }
}

Java8 新增的重复注解

在同一个元素类型前,多次使用多个相同类型的注解,就是重复注解。

Java8 允许使用多个相同类型的 Annotation 来修饰同一个元素。

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Authority {
    String role();
}

@Repeatable(Authorities.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Authorities {
    Authority[] value();
}

public class RepeatAnnotationUseNewVersion {
    // 重复注解
    @Authority(role="Admin")
    @Authority(role="Manager")
    public void doSomeThing(){ }
}

欢迎关注我的公众号

Java 基础篇之注解的更多相关文章

  1. Java基础教程:注解

    Java基础教程:注解 本篇文章参考的相关资料链接: 维基百科:https://zh.wikipedia.org/wiki/Java%E6%B3%A8%E8%A7%A3 注解基础与高级应用:http: ...

  2. 小白—职场之Java基础篇

    java基础篇 java基础 目录 1.java是一种什么语言,jdk,jre,jvm三者的区别 2.java 1.5之后的三大版本 3.java跨平台及其原理 4.java 语言的特点 5.什么是字 ...

  3. Java基础笔记 – Annotation注解的介绍和使用 自定义注解

    Java基础笔记 – Annotation注解的介绍和使用 自定义注解 本文由arthinking发表于5年前 | Java基础 | 评论数 7 |  被围观 25,969 views+ 1.Anno ...

  4. java基础篇---I/O技术

    java基础篇---I/O技术   对于任何程序设计语言而言,输入输出(I/O)系统都是比较复杂的而且还是比较核心的.在java.io.包中提供了相关的API. java中流的概念划分 流的方向: 输 ...

  5. 金三银四跳槽季,BAT美团滴滴java面试大纲(带答案版)之一:Java基础篇

    Java基础篇: 题记:本系列文章,会尽量模拟面试现场对话情景, 用口语而非书面语 ,采用问答形式来展现.另外每一个问题都附上“延伸”,这部分内容是帮助小伙伴们更深的理解一些底层细节的补充,在面试中可 ...

  6. java基础篇---HTTP协议

    java基础篇---HTTP协议   HTTP协议一直是自己的薄弱点,也没抽太多时间去看这方面的内容,今天兴致来了就在网上搜了下关于http协议,发现有园友写了一篇非常好的博文,博文地址:(http: ...

  7. java基础篇---I/O技术(三)

    接上一篇java基础篇---I/O技术(二) Java对象的序列化和反序列化 什么叫对象的序列化和反序列化 要想完成对象的输入或输出,还必须依靠对象输出流(ObjectOutputStream)和对象 ...

  8. Java基础篇 - 强引用、弱引用、软引用和虚引用

    Java基础篇 - 强引用.弱引用.软引用和虚引用 原创零壹技术栈 最后发布于2018-09-09 08:58:21 阅读数 4936 收藏展开前言Java执行GC判断对象是否存活有两种方式其中一种是 ...

  9. java基础篇 之 构造器内部的多态行为

    java基础篇 之 构造器内部的多态行为 ​ 我们来看下下面这段代码: public class Main { public static void main(String[] args) { new ...

随机推荐

  1. 2-删除IPC$的方式

    一.使用命令临时删除IPC$的方式 1.查看IPC$是否启用 命令:net share 2.删除IPC$功能 命令:net share ipc$ /delete 注:使用命令删除后,重启服务器后,IP ...

  2. numba初体验

    numba初体验 今天在知乎上发现了一个很神奇的包numba,可以用jit的方式大幅提高计算型python代码的效率,一起来看一下 安装 numba的安装方式很简单,使用pip或者anacoda都可以 ...

  3. SpringBoot AOP注解式拦截与方法规则拦截

    AOP的本质还是动态代理对方法调用进行增强. SpringBoot 提供了方便的注解实现自定义切面Aspect. 1.使用需要了解的几个概念: 切面.@Aspect 切点.@Pointcut. 通知. ...

  4. 012_linuxC++之_类的继承定义

    (一)访问控制和继承 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基 ...

  5. word文档的图片怎么保存到ueditor上

    word图片转存,是指UEditor为了解决用户从word中复制了一篇图文混排的文章粘贴到编辑器之后,word文章中的图片数据无法显示在编辑器中,也无法提交到服务器上的问题而开发的一个操作简便的图片转 ...

  6. 在 CentOS 7 上安装 RabbitMQ

    RabbitMQ 服务器在安装之前需要安装 erlang. 最新版本的 RabbitMQ 3.8.0 需要 Erlang 21.3 以上的版本支持. 在这里,我们需要在你的 CentOS 中安装 Er ...

  7. [Luogu] 聪明的质监员

    https://www.luogu.org/problemnew/show/P1314 满足单调性 所以,二分W,进行检验 #include <iostream> #include < ...

  8. HGOI 20191108 题解

    Problem A 新婚快乐 一条路,被$n$个红绿灯划分成$n+1$段,从前到后一次给出每一段的长度$l_i$,每走$1$的长度需要$1$分钟. 一开始所有红绿灯都是绿色的,$g$分钟后所有红绿灯变 ...

  9. IP地址正则表达式的写法

    IP地址的正则表达式写法 这里讲的是IPv4的地址格式,总长度 32位=4段*8位,每段之间用.分割, 每段都是0-255之间的十进制数值. 将0-255用正则表达式表示,可以分成一下几块来分别考虑: ...

  10. Namenode服务挂

    BUG修复:HDFS-13112 这两天排查了小集群Crash的问题,这里先总结下这两天排查的结果 一.查看日志 首先查看了Namenode Crash的时候的日志 (一)以下是patch hdfs- ...