再谈谈注解

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

注解,和反射一样,是Java中最重要却最容易被人遗忘的知识点。哪怕Spring、SpringMVC、SpringBoot等框架中充满了注解,我们还是选择性地忽视它。很多人不明白它是怎么起作用的,甚至有人把它和注释混淆...工作中也只是机械性地在Controller上加@RequestMapping。是的,我们太习以为常了,以至于觉得它理所应当就是如此。

两件小事

我刚入行不久后遇到的两件事让我对注解有了新的认识。

第一件事是,同年6月我去了趟北京,参与开发了某中国五百强企业的一个加密系统,第一次接触到了SpringBoot。当我发现一个demo项目只要简单地写个启动类并加上@SpringBootApplication就可以直接访问Controller时,感到非常震撼,我裂开了。

整个demo没有一个配置文件,连web.xml也没有。

由于开发进度很赶,当时没时间去研究它是如何做到的,但这件事让我意识到自己对注解还是了解得太少。

第二件事是,年底跳槽回到杭州后我又参与开发了一个金融借贷系统,那阵子对接了很多第三方的风控接口:

对签名验签不了解的朋友,可以百度一下。总之,每对接一个接口,都要在开头进行数据校验。一两个接口也就算了,但每次对接风控,基本上都要写10+多个方法。每个方法开头都写一份签名验签的代码,显然太冗余了。我当时的做法是将验签代码抽取成方法,方便复用,自以为算是一种改良了,直到我看到同事用了切面...40米的大刀拦腰砍去,给每个方法都做了签名验签:

注意,实际上切面的作用是在方法前后,而不是方法内部的前后。上面这样画,仅仅为了更形象。

这两件事,让我知道,是时候重新学习一下注解了。

注解概述

格式

public @interface 注解名称{
    属性列表;
}

格式有点奇怪,我们稍后再研究。

分类

大致分为三类:自定义注解、JDK内置注解、还有第三方框架提供的注解。

  • 自定义注解就是我们自己写的注解,比如@UserLog
  • JDK内置注解,比如@Override检验方法重写,@Deprecated标识方法过期等
  • 第三方框架定义的注解比如SpringMVC的@Controller等

使用位置

实际开发中,注解常常出现在类、方法、成员变量、形参位置。当然还有其他位置,这里不提及。

作用

如果说注释是写给人看的,那么注解就是写给程序看的。它更像一个标签,贴在一个类、一个方法或者字段上。它的目的是为当前读取该注解的程序提供判断依据及少量附加信息。比如程序只要读到加了@Test的方法,就知道该方法是待测试方法,又比如@Before注解,程序看到这个注解,就知道该方法要放在@Test方法之前执行。有时我们还可以通过注解属性,为将来读取这个注解的程序提供必要的附加信息,比如@RequestMapping("/user/info")提供了Controller某个接口的URL路径。

级别

注解和类、接口、枚举是同一级别的。

注解的本质

@interface和interface从名字上看非常相似,我猜注解的本质是一个接口(当然,这是瞎猜)。

为了验证这个猜测,我们做个实验。先按上面的格式写一个注解(暂时不附加属性):

编译后得到字节码文件:

通过XJad工具反编译MyAnnotation.class:

我们发现,@interface变成了interface,而且自动继承了Annotation:

既然确实是个接口,那么我们自然可以在里面写方法

得到class文件后反编译

由于接口默认方法的修饰符就是public abstract,所以可以省略,直接写成:

/**
 * @author mx
 */
public @interface MyAnnotation {
    String getValue();
}

虽说注解的本质是接口,但是仍然有很多怪异的地方,比如使用注解时,我们竟然可以给getValue()赋值:

/**
 * @author mx
 */
@MyAnnotation(getValue = "annotation on class")
public class Demo {

    @MyAnnotation(getValue = "annotation on field")
    public String name;

    @MyAnnotation(getValue = "annotation on method")
    public void hello() {}

}

你见过给方法赋值的操作吗?(别闹了,你脑中想到的是给方法传参)。

虽然这里的getValue可能不是指getValue(),底层或许是getValue()返回的一个同名变量。但不管怎么说,还是太怪异了。所以在注解里,类似于String getValue()这种,被称为“属性”,而给属性赋值显然听起来好接受多了。

另外,我们还可以为属性指定默认值:

当没有赋值时,属性将使用默认值,比如上面的defaultMethod(),它的getValue就是“no description"。

基于以上差异,以后还是把注解单独归为一类,不要当成接口使用。

反射注解信息

上文已经说过,注解就像一个标签,是贴在程序代码上供另一个程序读取的。所以三者关系是:

要牢记,只要用到注解,必然有三角关系:

  • 定义注解
  • 使用注解
  • 读取注解

仅仅完成前两步,是没什么卵用的。就好比你写了一本武林秘籍却没人去学,那么这门武功还不如一把菜刀。

所以,接下来需要我们编写一个程序读取注解。读取注解的思路是:

反射获取注解信息:

/**
 * @author mx
 */
public class AnnotationTest {
    public static void main(String[] args) throws Exception {
        // 获取类上的注解
        Class<Demo> clazz = Demo.class;
        MyAnnotation annotationOnClass = clazz.getAnnotation(MyAnnotation.class);
        System.out.println(annotationOnClass.getValue());

        // 获取成员变量上的注解
        Field name = clazz.getField("name");
        MyAnnotation annotationOnField = name.getAnnotation(MyAnnotation.class);
        System.out.println(annotationOnField.getValue());

        // 获取hello方法上的注解
        Method hello = clazz.getMethod("hello", (Class<?>[]) null);
        MyAnnotation annotationOnMethod = hello.getAnnotation(MyAnnotation.class);
        System.out.println(annotationOnMethod.getValue());

        // 获取defaultMethod方法上的注解
        Method defaultMethod = clazz.getMethod("defaultMethod", (Class<?>[]) null);
        MyAnnotation annotationOnDefaultMethod = defaultMethod.getAnnotation(MyAnnotation.class);
        System.out.println(annotationOnDefaultMethod.getValue());

    }
}

Class、Method、Field对象都有个getAnnotation()方法,可以获取各自位置上的注解信息,但IDEA好像提示我们错误:

Annotation 'MyAnnotation.class' is not retained for reflective。

直译的话就是:注解MyAnnotation并没有为反射保留。

我不管,我要开搞了,哪轮得到你一个编译器在这瞎比比,直接run一下:

不听老人言,吃亏在眼前。

这是因为注解其实有所谓“保留策略”的说法。大家学习JSP时,应该学过<!-- -->和<%-- -->的区别:前者可以在浏览器检查网页源代码时看到,而另一个在服务器端输出时就被抹去了。同样的,注解通过保留策略,控制自己可以保留到哪个阶段。保留策略也是通过注解实现,它属于元注解,也叫元数据。

元注解

所谓元注解,就是加在注解上的注解。作为普通程序员,常用的就是:

  • @Documented:用于制作文档,不是很重要,忽略便是
  • @Target:加在注解上,限定该注解的使用位置。不写的话,好像默认各个位置都是可以的。

所以如果需要限定注解的使用位置,可以在自定义的注解上使用@Target(普通注解上使用元注解)。我们本次默认即可,不特别限定。

  • @Retention(注解的保留策略)

注解的保留策略有三种:SOURCE/ClASS/RUNTIME

“保留策略”这个元注解的意义在哪呢?

一般来说,普通开发者使用注解的时机都是运行时,比如反射读取注解(也有类似Lombok这类编译期注解)。既然反射是运行时调用,那就要求注解的信息必须保留到虚拟机将.class文件加载到内存为止。如果你需要反射读取注解,却把保留策略设置为RetentionPolicy.SOURCE、RetentionPolicy.CLASS,那就读取不到了。

现在,我们已经完成了使用注解的三部曲:

定义注解

/**
 * @author mx
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String getValue() default "no description";
}

使用注解

/**
 * @author mx
 */
@MyAnnotation(getValue = "annotation on class")
public class Demo {

    @MyAnnotation(getValue = "annotation on field")
    public String name;

    @MyAnnotation(getValue = "annotation on method")
    public void hello() {}

    @MyAnnotation() // 故意不指定getValue
    public void defaultMethod() {}
}

读取注解信息

注意,defaultMethod()反射得到的注解信息是:no description,就是MyAnnotion中getValue的默认值。

但是,注解的读取并不只有反射一种途径。比如@Override,它由编译器读取(你写完代码ctrl+s时就编译了),而编译器只是检查语法错误,此时程序尚未运行。

保留策略为SOURCE,仅仅是源码阶段,编译成.class文件后就消失

属性的数据类型及特别的属性:value和数组

属性的数据类型

  • 八种基本数据类型
  • String
  • 枚举
  • Class
  • 注解类型
  • 以上类型的一维数组
/**
 * @author mx
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
   // 8种基本数据类型
    int intValue();
    long longValue();
    // ...其他类型省略

    // String
    String name();
    // 枚举
    CityEnum cityName();
    // Class类型
    Class<?> clazz();
    // 注解类型
    MyAnnotation2 annotation2();

    // 以上几种类型的数组类型
    int[] intValueArray();
    String[] names();
    // ...其他类型省略
}

@interface MyAnnotation2 {
}

enum CityEnum {
    BEIJING,
    HANGZHOU,
    SHANGHAI;
}

以Demo类上注解为例,演示给注解属性赋值多种类型:

/**
 * @author mx
 */
@MyAnnotation(
        // 8种基本类型
        intValue = 1,
        longValue = 0L,

        // String
        name = "annotation on class",
        // 枚举
        cityName = CityEnum.BEIJING,
        // Class
        clazz = Demo.class,
        // 注解
        annotation2 = @MyAnnotation2,
        // 一维数组
        intValueArray = {1, 2},
        names = {"Are", "you", "OK?"}
)
public class Demo {
    // 省略...
}

value属性

如果注解的属性只有一个,且叫value,那么使用该注解时,可以不用指定属性名,因为默认就是给value赋值:

但是注解的属性如果有多个,无论是否叫value,都必须写明属性的对应关系:

按IDEA的提示修正:

数组属性

如果数组的元素只有一个,可以省略花括号{}:

用常量类为注解属性赋值

如果你希望为注解的属性提供统一的几个可选值,可以使用常量类:

或者:

本质其实还是String,只不过是通过常量表现。上面只是举个例子,大家可以根据实际业务自由发挥。

小结

  • 注解就像标签,是程序判断执行的依据。比如,程序读到@Test就知道这个方法是待测试方法,而@Before的方法要在测试方法之前执行
  • 注解需要三要素:定义、使用、读取并执行
  • 注解分为自定义注解、JDK内置注解和第三方注解(框架)。自定义注解一般要我们自己定义、使用、并写程序读取,而JDK内置注解和第三方注解我们只要使用,定义和读取都交给它们
  • 大多数情况下,三角关系中我们只负责使用注解,无需定义和执行,框架会将注解类读取注解的程序隐藏起来,除非阅读源码,否则根本看不到。平时见不到定义和读取的过程,光顾着使用注解,久而久之很多人就忘了注解如何起作用了!

关于注解的使用案例,请参考下篇。

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

进群,大家一起学习,一起进步,一起对抗互联网寒冬

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/171197.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

浅谈 JVM GC 收集器--系列(一)

又到一年大促时刻&#xff0c;今天我们一起探讨下JVM垃圾回收的问题&#xff0c;写代码的时候想一想如何减少FullGC问题的出现&#xff0c;因为一旦出现频繁FullGC&#xff0c;短时间内没有太好的解决办法&#xff0c;很有可能重启后服务接着FullGC&#xff0c;导致服务可用率降…

【离散数学】——刷题题库(范式)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

Codeforces Round 910 (Div. 2) --- B-E 补题记录

B - Milena and Admirer Problem - B - Codeforces 题目大意&#xff1a; 现在给出一个无序序列&#xff0c;你可以使用任意次操作将这个无序序列修改为不递减序列&#xff0c;操作为你可以使用两个数a和b来替换ai&#xff0c;序列就变为了 ai-1&#xff0c; a&#xff0c;…

Flink Operator 使用指南 之 Flink Operator安装

介绍 Flink Kubernetes Operator 充当控制平面来管理 Apache Flink 应用程序的完整部署生命周期。尽管 Flink 的Native Kubernetes 集成已经允许用户在运行的 Kubernetes(k8s) 集群上直接部署 Flink 应用程序,但自定义资源和Operator Pattern 也已成为 Kubernetes 原生部署体…

# 学习 Prolog 和 离散逻辑的16个等价公式:一趟有趣的逻辑之旅

Prolog 的语法很奇怪,需要一些时间来适应,所以我花了点时间,想用Prolot来表示和验证离散逻辑的16组等价公式。 1. 双重否定律 (Double Negation Law) A ⇔A 首先&#xff0c;我们来看看双重否定律。在 Prolog 中&#xff0c;我们可以这样验证它&#xff1a; fun1(A,Z):-membe…

RK3588产测软件介绍

1. 简介 本公司研发的产测软件是用于在量产的过程中快速地甄别产品功能和器件的好坏&#xff0c;即重点 FCT&#xff08;Functional Test&#xff09;测试&#xff0c;进而提高生产效率和检测的准确性。 2. 产测软件介绍 QT开发的ARM平台产测图形化软件&#xff0c;一键开启傻…

『C++成长记』类和对象

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;C &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、类的引入 二、类的定义 三、类的访问限定符 四、类的作用域 五、类的实例化…

基于V100下Llama2-Atom大模型微调

文章目录 大规模的中文数据预训练模型部署模型微调Step1: 环境准备Step2: 数据准备Step3: 微调脚本Step4: 加载微调模型 一些BUG 大规模的中文数据预训练 原子大模型Atom在Llama2的基础上&#xff0c;采用大规模的中文数据进行持续预训练&#xff0c;包含百科、书籍、博客、新…

⑩⑦【MySQL】锁:全局锁、表级锁、行级锁

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ MySQL锁 ⑩⑦【MySQL】锁&#xff1a;全局锁、…

官宣!Sam Altman加入微软,OpenAI临时CEO曝光,回顾董事会‘’政变‘’始末

11月20日下午&#xff0c;微软首席执行官Satya Nadella在社交平台宣布&#xff0c;“微软仍然致力于与 OpenAI的合作伙伴关系。同时欢迎Sam Altman 和 Greg Brockman 及其团队加入微软&#xff0c;领导一个全新的AI研究团队”。 Sam第一时间对这个消息进行了确认。 此外&…

【机器学习】对比学习(contrastive learning)

对比学习是一种机器学习技术&#xff0c;算法学习区分相似和不相似的数据点。对比学习的目标是学习数据的表示&#xff0c;以捕捉不同数据点之间的基本结构和关系。 在对比学习中&#xff0c;算法被训练最大化相似数据点之间的相似度&#xff0c;并最小化不相似数据点之间的相似…

LOJ #10134. 「一本通 4.4 练习 1」Dis

分析 根据数据范围分析一下复杂度&#xff0c;Floyd和dj算法都必爆。 发现题目说的是树&#xff0c;还是边还是双向的&#xff08;树本身就是无向的&#xff0c;连通无回路的无向图叫做无向树&#xff0c;简称树。如果题目说了树&#xff0c;那么默认边就是双向的&#xff09…

优思学院|现代质量管理实践与六西格玛方法论如何融合?

企业要解决质量问题必然需要涉及管理&#xff0c;然而&#xff0c;如果仅仅将六西格玛法视为一种质量管理方法&#xff0c;必定会导致六西格玛管理法的失败。六西格玛法是一种具有特定战略性的管理方法&#xff0c;它涉及到市场、顾客、产品、服务、流程、质量、价值链以及财务…

利用多核的Rust快速Merkle tree

1. 引言 利用多核的Rust快速Merkle tree&#xff0c;开源代码见&#xff1a; https://github.com/anoushk1234/fast-merkle-tree&#xff08;Rust&#xff09; 其具有如下属性&#xff1a; 可调整为任意高度构建root复杂度为O(n)提供了插入和获取叶子节点的方法获取某叶子节…

centos7 利用nc命令探测某个tcp端口是否在监听

脚本 # 安装nc yum install -y ncnc -vz 192.168.3.128 60001 if [ $? -eq 0 ]; thenecho "tcp succeed" elseecho "tcp failed" fi nc -vz 192.168.3.128 60001 探测192.168.3.128服务器上60001 tcp端口, -vz说明是探测TCP的 端口开启的情况 执行…

用不用Microsoft Defender是你的自由,但不用最好也得有替代品

Microsoft Defender是预装在Windows 11操作系统上的重要安全工具。安全套件已完全集成到操作系统中&#xff0c;以保护你的系统免受恶意软件的攻击&#xff0c;但并不是每个人都喜欢它。你是否更愿意安装另一种防病毒/反间谍软件&#xff0c;以将Microsoft Defender推向绝境&am…

ATA-304功率放大器的电子实验案例(案例合集)

ATA-304功率放大器凭借其优异的指标参数受到不少电子工程师的喜欢&#xff0c;其在电子实验中的应用也非常频繁&#xff0c;下面为大家整理出ATA-304功率放大器的应用案例合集&#xff0c;希望能对领域内各位工程师、研究人员有所帮助。 案例一&#xff1a;ATA-304功率放大器在…

并行与分布式 第4章 数据级并行:向量体系结构和GPU

文章目录 并行与分布式 第4章 数据级并行&#xff1a;向量体系结构和GPU4.1 什么叫数据级并行4.1.1 数据级并行与SPMD4.1.2数据级并行——传统器件的问题4.1.3 数据级并行——向量体系结构和GPU 4.2 向量体系结构4.2.1 向量以及计算方式4.2.2 向量体系结构4.2.3 向量运算的执行…

如何在公网环境下使用内网穿透工具实现用ipad pro进行代码开发

文章目录 前言1. 本地环境配置2. 内网穿透2.1 安装cpolar内网穿透(支持一键自动安装脚本)2.2 创建HTTP隧道 3. 测试远程访问4. 配置固定二级子域名4.1 保留二级子域名4.2 配置二级子域名 5. 测试使用固定二级子域名远程访问6. iPad通过软件远程vscode6.1 创建TCP隧道 7. ipad远…

解锁数据库运维秘籍:掌握AntDB-T动态共享内存,提升进程间通信效率

动态共享内存是AntDB数据库通信的重要手段&#xff0c;本文主要阐述AntDB-T数据库动态共享内存的实现原理、实现方式与使用方法。 AntDB-T数据库是一款企业级通用分布式关系型数据库&#xff0c;其数据库内核是基于进程模型实现的&#xff0c;因此进程间通信&#xff08;IPC&am…