再谈谈注解

作者简介:大家好,我是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() // 故意不指定getValuepublic 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();// ...其他类型省略// StringString 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,// Stringname = "annotation on class",// 枚举cityName = CityEnum.BEIJING,// Classclazz = 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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

成都瀚网科技有限公司抖音带货靠谱么

近年来&#xff0c;随着社交媒体的兴起&#xff0c;越来越多的企业开始利用抖音等短视频平台进行产品推广和销售。成都瀚网科技有限公司也紧跟潮流&#xff0c;通过抖音平台进行带货。那么&#xff0c;成都瀚网科技有限公司的抖音带货靠谱么&#xff1f;本文将从以下几个方面进…

网站监控的重要性及实施策略

随着互联网的快速发展&#xff0c;网站已经成为企业和个人不可或缺的在线服务平台。然而&#xff0c;网站的安全性和稳定性一直是企业及个人非常关注的问题。一旦网站出现故障或者被攻击&#xff0c;将会给企业和个人带来严重的损失。因此&#xff0c;实施有效的网站监控策略对…

Node.js之Buffer(缓冲器)

Buffer的概念 Buffer 是一个类似于数组的 对象&#xff0c;用于表示固定长度的字节序列 Bufer 本质是一段内存空间&#xff0c;专门用来处理 二进制数据。 Buffer创建方法 // 1.alloc let buf1 Buffer.alloc(10) // 使用alloc创造buffer的方法 是一个二进制类 都会归零 cons…

为什么 Django 后台管理系统那么“丑”?

哈喽大家好&#xff0c;我是咸鱼 相信使用过 Django 的小伙伴都知道 Django 有一个默认的后台管理系统——Django Admin 它的 UI 很多年都没有发生过变化&#xff0c;现在看来显得有些“过时且简陋” 那为什么 Django 的维护者却不去优化一下呢&#xff1f;原文作者去询问了多…

如何选择合适的域名注册商?需要考虑哪些方面因素?

随着互联网的快速发展&#xff0c;涌现出大大小小的众多域名注册服务商&#xff0c;但这些域名注册服务商在技术、服务、价格等方面参差不齐&#xff0c;给域名注册者带来了很多困扰。那么该如何选择合适的域名注册商呢&#xff1f;选择域名注册商需要考虑哪些方面呢&#xff1…

RT-DETR手把手教程,注意力机制如何添加在网络的不同位置进行创新优化

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文独家改进&#xff1a;本文首先复现了将EMA引入到RT-DETR中&#xff0c;并跟不同模块进行结合创新&#xff1b;1&#xff09;Rep C3结合&#xff1b;2&#xff09;直接作为注意力机制放在网络不同位置&#xff1b;3&#xff09;高效…

django DRF认证组件示例

一、学习DRF的认证类&#xff1b; 设计&#xff1a;LoginView不登录就可以访问&#xff0c;UserView和OrderView需要通过认证后才能访问&#xff1b; 1、urls.py urlpatterns [path(login/, views.LoginView.as_view()),path(user/, views.UserView.as_view()),path(order/,…

mysql8.0英文OCP考试第131-140题

Q131.You have upgraded the MySQL binaries from 5.7.28 to 8.0.18 by using an in-place upgrade. Examine the message sequence generated during the first start of MySQL 8.0.18: 。。。[System]。。。/usx/sbin/mysqld (mysqld 8.0.18-commercial) starting as proces…

女儿冬天的第一件羽绒服,这也太好看了

分享女儿的时尚穿搭 撞色插肩款羽绒服 同色系的精彩碰撞 描绘出绚烂的色彩 走在街上就是最靓的崽 显肤色显瘦超吸睛 妥投时尚小潮人一枚

如何将图片转为excel或word?(客户端)

演示软件&#xff1a;金鸣表格文字识别大师3.6.1&#xff08;新版本界面可能会略有不同&#xff09; 第一部分 将图片转为excel或文表混合的word 一般的软件要将图片转为可编辑的excel&#xff0c;都需要待识别的图片要有明显清晰的表格线&#xff0c;但我们程序现已克服了这…

Java和 JS 的10大不同之处,你清楚吗?

还记得刚开始学习编程时&#xff0c;我就在想&#xff1a;“Java和JavaScript是同一种语言吗&#xff1f;”。就是因为看到它们名称中都带“java”&#xff0c;所以才会误以为它们有关系。实际上&#xff0c;它们并没有太大的联系。 这两者的关系&#xff0c;就和英语与斯瓦希…

【数据结构】图的存储结构(邻接矩阵)

一.邻接矩阵 1.图的特点 任何两个顶点之间都可能存在边&#xff0c;无法通过存储位置表示这种任意的逻辑关系。 图无法采用顺序存储结构。 2.如何存储图&#xff1f; 将顶点与边分开存储。 3.邻接矩阵&#xff08;数组表示法&#xff09; 基本思想&#xff1a; 用一个一维数…

jenkins-2.426.1-1.1.noarch.rpm 的公钥没有安装

执行命令 yum install jenkins 报错 jenkins-2.426.1-1.1.noarch.rpm 的公钥没有安装 下载的软件包保存在缓存中&#xff0c;直到下次成功执行事务。 您可以通过执行 yum clean packages 删除软件包缓存。 错误&#xff1a;GPG 检查失败 解决办法&#xff1a; 1、安装新的公…

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

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

用于神经网络的FLOP和Params计算工具

用于神经网络的FLOP和Params计算工具 1. FlopCountAnalysis pip install fvcoreimport torch from torchvision.models import resnet152, resnet18 from fvcore.nn import FlopCountAnalysis, parameter_count_tablemodel resnet152(num_classes1000)tensor (torch.rand(1…

vue-waterfall2 实现瀑布流,及总结的问题

注意&#xff1a;引入需要在主界面引入&#xff0c;直接在组件中引用会有问题 1.安装 npm install vue-waterfall21.8.20 --save &#xff08;提示&#xff1a;一定要安装1.8.20&#xff0c;最新版会有一部分问题&#xff09; 2.打开main.js文件 import waterfall from v…

微服务实战系列之Gateway

前言 人类世界自工业革命以来&#xff0c;无论从金融、货币、制度&#xff0c;还是科技、资源、社会各个方面&#xff0c;都发生了翻天覆地的变化。物质极大丰富&#xff0c;从而也推动了科技的极速发展。当计算机问世也仅仅不到80年&#xff0c;而如今我们的生活中处处有它的影…

云原生Docker系列 | Docker私有镜像仓库公有镜像仓库使用

云原生Docker系列 | Docker私有镜像仓库&公有镜像仓库使用 1. 使用公有云镜像仓库1.1. 阿里云镜像仓库1.2. 华为云镜像仓库1.3. 腾讯云镜像仓库2. 使用Docker Hub镜像仓库3. 使用Harbor构建私有镜像仓库4. 搭建本地Registry镜像仓库1. 使用公有云镜像仓库 1.1. 阿里云镜像…

GNSS位移监测站系统是什么

WX-WY4G 一、GNSS位移监测站系统的工作原理GNSS位移监测站系统是一种基于导航卫星系统&#xff08;GNSS&#xff09;的高精度位移监测技术。它通过接收和处理来自卫星的信号&#xff0c;对地表物体的位置进行精度的实时监测。这个系统具有可靠性的特点&#xff0c;被广泛应用于…

ubuntu20.04.1网络图标突然消失,无法上网

故障&#xff1a;打开虚拟机进入Ubuntu系统后&#xff0c;打开火狐浏览器&#xff0c;发现无法连接网络。 解决办法&#xff1a;因为刚接触Linux系统&#xff0c;就在网上找各种资料&#xff0c;试了各种办法无果&#xff0c;最后发现有可能网络配置文件被更改。 打开控制台输…