当前位置:网站首页 > 技术博客 > 正文

java注解工作原理



哈喽,我是狗哥。随着开发经验的累积,我越发觉得基础真的非常重要。比如:大部分框架 (如 Spring) 都使用了注解简化代码并提高编码的效率,掌握注解是一名 JAVA 程序员必备的技能。

但我发现很多工作 2、3 年的同学居然还没写过自定义注解,问起注解的原理也是一脸懵。我是很震惊的,你们咋理解代码的?基于此,今天我们就来一起学习下注解。

国际惯例,先上脑图:

Java 注解(Annotation),相信大家没用过也见过。个人理解,注解就是代码中的特殊标记,这些标记可以在编译、类加载、运行时被读取,从而做相对应的处理

注解跟注释很像,区别是注释是给人看的(想想自己遇到那些半句注释没有的业务代码,还是不是很难受?);而注解是给程序看的,它可以被编译器读取

注解大多时候与反射或者 AOP 切面结合使用,它的作用有很多,比如标记和检查,最重要的一点就是简化代码,降低耦合性,提高执行效率。比如我司就是通过自定义注解 + AOP 切面结合,解决了写接口重复提交的问题。

简单描述下我司防止重复提交注解的逻辑:请求写接口提交参数 —— 参数拼接字符串生成 MD5 编码 —— 以 MD5 编码加用户信息拼接成 key,set Redis 分布式锁,能获取到就顺利提交(分布式锁默认 3 秒过期),不能获取就是重复提交了,报错

如果每加一个写接口,就要写一次以上逻辑的话,那程序员会疯的。所以,有大佬就使用注解 + AOP 切面的方式解决了这个问题。只要在写接口 Controller 方法上加这个注解即可解决,也方便维护

以我司防止重复提交的自定义注解,介绍下注解的语法。它的定义如下:

Java 注解使用 修饰,我司的 注解也不例外。此外,还使用两个元注解。其中 注解传入 参数来标明 只能用于方法上, 则用来表示该注解生存期是运行时,从代码上看注解的定义很像接口的定义,在编译后也会生成 文件。

定义在注解内部的变量,称之为元素。注解可以有元素,也可以没有元素。像 就是无元素的注解, 就属于有元素的注解。

带元素的自定义注解:

1.3.1 注解元素的格式

注解的元素格式如下:

1.3.2 注解元素的数据类型

注解元素支持如下数据类型:

声明注解元素时可以使用基本类型但不允许使用任何包装类型,同时注解也可以作为元素的类型,也就是嵌套注解

1.3.3 编译器对元素默认值的限制

遵循规则:

注解是以 @注释名 的格式在代码中使用,比如:以下常见的用法。

在 save 方法上使用 (我司自定义注解),加上之后,编译期会自动识别该注解并执行注解处理器的方法,防止重复提交;

而对于 和 ,则是 Java 的内置注解,前者意味着该方法是过时的,后者则是忽略指定的异常检查。

上面介绍注解的语法和使用,我们遇到了 等没见过的注解,你可能有点懵。但没关系,听我说道说道。Java 中有 和 等内置注解;也有 等修饰注解的注解,称之为元注解

Java 定义了一套自己的注解,其中作用在代码上的是:

JDK7 之后又加了 3 个,这几个的用法,我也用得很少。就不过多介绍了,感兴趣的小伙伴自行百度分别是:

元注解就是修饰注解的注解,分别有:

2.2.1 @Target

用来指定注解的作用域(如方法、类或字段),其中 ElementType 是枚举类型,其定义如下,也代表可能的取值范围

PS:如果 @Target 无指定作用域,则默认可以作用于任何元素上。等同于:

2.2.2 @Retention

用来指定注解的生命周期,它有三个值,对应 中的三个枚举值,分别是:源码级别(source),类文件级别(class)或者运行时级别(runtime)

2.2.2.1 理解 @Retention

这里引申一下话题,要想理解 @Retention 就要理解下从 java 文件到 class 文件再到 class 被 jvm 加载的过程了。下图描述了从 .java 文件到编译为 class 文件的过程:

其中有一个注解抽象语法树的环节,这个环节其实就是去解析注解然后做相应的处理。

所以重点来了,如果你要在编译期根据注解做一些处理,你就需要继承 Java 的抽象注解处理器 AbstractProcessor,并重写其中的 process () 方法。

一般来说只要是注解的 @Target 范围是 SOURCE 或 CLASS,我们就要继承它;因为这两个生命周期级别的注解等加载到 JVM 后,就会被抹除了

比如,lombok 就用 继承了 ,以实现编译期的处理。这也是为什么我们使用 就能实现 方法的原因。

2.2.3 @Documented

执行 javadoc 的时候,标记这些注解是否包含在生成的用户文档中。

2.2.4 @Inherited

标记这个注解具有继承性,比如 A 类被注解 @Table 标记,而 @Table 注解被 @Inherited 声明(具备继承性);继承于 A 的子类,也继承 @Table 注解。

好啦,说了这么多理论。大家也听累了,我也聊累了。那怎么自定义一个注解并让它起作用呢?下面我将带着你们看看我司的防止重复提交的注解是怎么实现的?当然,由于设计内部的东西,我只会写写伪代码。思路在前面介绍过了,为方便阅读我拿下来,大家理解就行。

需求是:同一用户,三秒内重复提交一样的参数,就会报异常阻止重复提交,否则正常提交处理写请求

首先,定义注解必须是 @interface 修饰;其次,有四个考虑的点:

基于此,我司的防止重复提交的自定义注解就出来了:

可以看到这里利用了 AOP 切面的方式获取被 @NoReSubmitLock 修饰的方法,并借此拿到切点(被注解修饰方法)的参数、用户信息等等,通过 MD5 处理,最终尝试上锁。

使用也非常简单,只需要一个注解就可以完成大部分的逻辑;如果不用注解,每个写接口的方法都要写一遍防止重复提交的逻辑的话,代码非常繁琐,难以维护。通过这个例子相信你也看到了,注解的作用。

本文介绍了注解的作用主要是标记、检查以及解耦;介绍了注解的语法;介绍了注解的元素以及传值方式;介绍了 Java 的内置注解和元注解,最后通过我司的一个实际例子,介绍了注解是如何起作用的?

注解是代码的特殊标记,可以在程序编译、类加载、运行时被读取并做相关处理。其对应 RetentionPolicy 中的三个枚举,其中 SOURCE、CLASS 需要继承 AbstractProcessor (注解抽象处理器),并实现 process () 方法来处理我们自定义的注解。而 RUNTIME 级别是我们常用的级别,结合 Java 的反射机制,可以在很多场景优化代码。

版权声明


相关文章:

  • 多层感知机bp算法2025-03-06 13:01:04
  • xml文件中的注释2025-03-06 13:01:04
  • crash分析vmcore2025-03-06 13:01:04
  • 滑动图块验证码如何解决2025-03-06 13:01:04
  • usb typec协议2025-03-06 13:01:04
  • 粒子群算法matlab程序2025-03-06 13:01:04
  • flex布局如何使用2025-03-06 13:01:04
  • mysql触发器触发条件2025-03-06 13:01:04
  • cas单点登录和sso2025-03-06 13:01:04
  • 代码在线对比2025-03-06 13:01:04