当前位置: 首页 > news >正文

注解原理剖析与实战

一、注解及其原理

在这里插入图片描述

1.注解的基本概念

注解,可以看作是对 一个类/方法的一个扩展的模版,每个类/方法按照注解类中的规则,来为类/方法注解不同的参数,在用到的地方可以得到不同的类/方法中注解的各种参数与值。
从JDK5开始,Java增加了对元数据(描述数据属性的信息)的支持。其实说白就是代码里的特殊标志,这些标志可以在编译、类加载和运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。

2.标准注解与元注解

2.1.标准注解

2.1.1.@Override

定义在 java.lang.Override中,此注解只能作用于方法,表示一个方法声明打算重写超类中的另一个方法声明,简单来说就是子类在重写父类方法的时候可以加上这个注解。默认情况下,子类重写的方法会自动覆盖父类的方法,但经验告诉我们,必须显式地在子类重写父类的方法上添加@Override注解来检查并标记子类是否重写了父类的方法。

2.1.2.@Deprecated

定义在java.lang.Deprecated中,此注解可以作用于方法、属性 和类等等,表示不建议程序员使用被@Deprecated所作用的对象,通常是因为它很危险或者存在更好的选择,即此对象已经过时,不推荐使用。例如:

  • 作用在方法上
    在这里插入图片描述在这里插入图片描述
  • 作用在类上
    在这里插入图片描述
    在这里插入图片描述

2.1.3.@SuppressWarnings

定义在 java.lang.SuppressWarnings 中,用来抑制编译时的警告信息,与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数都是已经定义好了的, 我们选择性的使用就好了,一般我们使用@SuppressWarnings(“all”)。

2.2.元注解

除了 Java 中为我们定义好的注解,我们还可以通过元注解来自定义注解, Java定义了4个标准的元注解(meta-annotation),用来定义和描述其他注解(自定义注解),所以也称为元数据注解。

2.2.1.@Target

用来描述注解的作用对象,即注解可以使用在什么地方,在定义注解的时候使用该注解可以清晰地知道它的使用范围,它的取值范围被定义在了一个枚举类中,常见的枚举值包括:

  • ElemenetType.CONSTRUCTOR 构造器声明 ;
  • ElemenetType.FIELD 域声明(包括 enum 实例);
  • ElemenetType.LOCAL_VARIABLE 局部变量声明;
  • ElemenetType.METHOD 方法声明;
  • ElemenetType.PACKAGE 包声明;
  • ElemenetType.PARAMETER 参数声明;
  • ElemenetType.TYPE 类,接口(包括注解类型)或enum声明;

2.2.2.@Retention

用来描述在什么级别保存该注解信息。可选的参数值在枚举类型 RetentionPolicy中,包括:

  • RetentionPolicy.SOURCE 注解将被编译器丢弃;
  • RetentionPolicy.CLASS 注解在class文件中可用,但会被VM丢弃;
  • RetentionPolicy.RUNTIME VM将在运行期也保留注解,因此可以通过反射工具读取注解的信息。

2.2.3.@Documented

将此注解包含在javadoc中,它代表着此注解会被javadoc工具提取成文档。在doc文档中的内容会因为此注解的信息内容不同而不同。相当与@see,@param等。

2.2.4.@Inherited

表示允许子类继承父类中的注解。

3.自定义注解

3.1.通过元注解定义注解

通过元注解,我们可以定义新的注解。长久以来,注解被视为一种轻量级的配置化技术方案,与XML相比,注解更加简单、便捷,但注解一般应用于比较简单的参数配置,而对于复杂的参数,就必须借助XML这个工具了。

3.1.1.定义注解及属性声明

  • 使用@interface关键字来定义新的注解。例如:
    在这里插入图片描述
    注解和类一样,也可以声明自己的属性,用来对注解自身进行描述。这样,原来写在配置文件中的信息,就可以通过注解的属性进行描述。
    1)定义注解的必录属性:String name(),在使用注解时,通过name=xxx来设置属性name的值,若没有设置属性name的值,系统会提示编译异常。
    2)定义注解属性的默认值:String name() default “test”,在使用注解时,若没有设置属性name的值,系统会自动将注解的name属性设置为默认值"test"。
    3)特殊属性value:如果注解@Query中只定义了一个名称为value的必录属性,那么使用注解时可以省略"value=",如@Query (“xxx”)。

3.1.2.定义不包含属性的注解

这类注解内部不存在任何属性,因此仅仅是起到一个标记的作用(与标记接口类似)。下面是一个不包含属性的自定义注解示例:
在这里插入图片描述

3.1.3.定义包含属性的注解

注解还可以定义自己的属性,这些属性可以用来存储一些关键、特征信息,以便程序通过当前注解的属性存储的信息来处理相应的业务逻辑。下面是一个包含属性的自定义注解示例:
在这里插入图片描述

3.1.3.1.注解属性支持的数据类型

注解的属性与类的属性除了声明方式存在差异外,还体现在所支持的数据类型。注解属性支持的数据类型包含以下几种:

  • 所有基本数据类型
  • 字符串类型String
  • 枚举类型enum
  • 注解类型Annotation
  • 以上数据类型的数组类型

例如:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意:如果你使用了其他数据类型,就会提示编译异常。

3.2.注解的使用

在上文中,我们定义了方法注解,因此可以作为方法级的轻量级配置使用,具体使用方式如下所示:
在这里插入图片描述

二、Spring注解原理剖析

Spring注解是自定义注解在实际开发中的最佳实践之一,一般而言,我接触到的Spring注解——@Controller、@Service和@Autowired等注解都是运行时注解,因此底层都是基于Java反射实现的。下面我们通过一个实际案例来深入理解Spring框架中注解的底层实现原理。

1.自定义自动注入注解思路点拨

思路点拨:由于Service类应用于Controller类中,因此,可以通过切面拦截Controller类,而切面无法拦截类上的注解,只能拦截方法上的注解,拦截到目标类的所有方法后,就遍历Controller类的所有属性,并判断属性上是否标记了@AutoInjected注解,再通过反射将实现类的实例注入到该属性中。

2.自定义@AutoInjected注解

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoInjected {
}

3.创建AutoInjectedAspect切面

import com.zh.test.annotation.AutoInjected;
import com.zh.test.system.utils.ApplicationUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.Field;@Aspect
@Component
public class AutoInjectedAcpect {@Pointcut("execution(* com.zh.test..*.controller..*.*(..))")public void beforeDoPointcut() {}@Before("beforeDoPointcut()")public void beforeDo(JoinPoint joinPoint) {Object target = joinPoint.getTarget();// 判断目标类是否标记了@RestController或@Controller注解RestController restController = AnnotationUtils.findAnnotation(target.getClass(), RestController.class);if(restController == null) {Controller controller = AnnotationUtils.findAnnotation(target.getClass(), Controller.class);if(controller == null) {return;}}Field[] fields = target.getClass().getDeclaredFields();for(Field field : fields) {// 获取属性上的注解AutoInjected autoInjected = field.getAnnotation(AutoInjected.class);// 如果为空,说明该属性上没有标记@AutoInjected注解,不予处理if(autoInjected == null) {continue;}Class<?> fieldType = field.getType();// 获取属性对应接口类型的实现类的实例Object bean = ApplicationUtil.getBean(fieldType);try {field.setAccessible(true);// 将属性对应接口类型的实现类的实例属注入到属性中field.set(target, bean);} catch (IllegalAccessException e) {throw new RuntimeException(e.getMessage());}}}
}

4.@AutoInjected注解的应用

import com.zh.test.annotation.AutoInjected;
import com.zh.test.system.entity.SysPageMeta;
import com.zh.test.system.service.IDemoService;
import com.zh.test.system.service.IMetaService;
import com.zh.test.system.service.impl.DemoServiceImpl;
import com.zh.test.system.utils.ApplicationUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.Field;
import java.util.Map;@RequestMapping("/demo")
@RestController
public class DemoController {@AutoInjectedprivate IDemoService service;@AutoInjectedprivate IMetaService metaService;@GetMapping("/queryByPage")public Map<String, Object>  queryByPage() {Map<String, Object> result = service.queryByPage();return result;}@PostMappingpublic Boolean addMetadata() {boolean result = metaService.addMetadata(new SysPageMeta());return result;}}

三、注解的实际应用

在第一章中,我们自定义的注解都是运行时注解,因此可以通过反射工具获取注解信息。不同的作用对象,获取注解的方式也不相同,下面我们举例说明。

1.通过切面实现自定义(方法)注解

当注解定义在类或方法上时,可以通过切面拦截并获取注解实例。在SpringBoot项目中,如果要使用切面,就必须引入以下的jar包依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

可以通过注解@Aspect和@Component来定义一个切面类:
在这里插入图片描述
具体示例如下:

@Aspect
@Component
public class QueryAspect {@Before("@annotation(query)")public void beforeQuery(JoinPoint joinPoint, Query query) {// 数据库String dbName = query.dbName();// 表名String tableName = query.tableName();// 是否分页boolean paginate = query.paginate();// 字段信息Column[] columns = query.columns();System.out.println("dbName:" + dbName);System.out.println("tableName:" + tableName);System.out.println("paginate:" + paginate);System.out.println("columns:" + Arrays.toString(columns));}
}

注意:当切面的切点表达式为@annotation(xxx)时,目标注解必须作用于目标对象的方法上,否则切面将无法拦截到目标注解!

2.通过ConstraintValidator校验器自定义方法参数注解

当注解定义在方法参数上时,无法通过切面拦截并获取注解实例,此时就必须借助ConstraintValidator校验器来获取注解信息。示例如下:

  • 引入hibernate-validator.jar包依赖
<dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.0.0.Final</version>
</dependency>
  • 定义用于辅助校验的注解

例子如下:

// 注解作用对象为类和方法参数
@Target({ElementType.TYPE, ElementType.PARAMETER})
// 运行时类型的注解,可通过反射工具获取当前注解实例
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExtCheck {String message() default "非法扩展字段";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};
}

注意:辅助校验的注解必须至少包含message、groups和payload三个最基本的成员属性,否则将抛出运行时异常。

  • 实现ConstraintValidator接口
    例子如下:
    在这里插入图片描述
  • 在@ExtCheck注解上添加上述实现的ConstraintValidator校验器
    例子如下:
    在这里插入图片描述
  • 定义全局异常处理器
    例子如下:
import com.zh.test.exception.ExtException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;/*** 全局异常处理器*/
@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(ExtException.class)public String handleAccessDeniedException(ExtException e, HttpServletRequest request) {return e.getMessage();}
}public class ExtException extends RuntimeException {public ExtException() {super();}public ExtException(String message) {super(message);}}
  • 在Controller层的类或方法参数前添加@Validated(必须)和@ExtCheck注解
    在这里插入图片描述

相关文章:

注解原理剖析与实战

一、注解及其原理 1.注解的基本概念 注解&#xff0c;可以看作是对 一个类/方法的一个扩展的模版&#xff0c;每个类/方法按照注解类中的规则&#xff0c;来为类/方法注解不同的参数&#xff0c;在用到的地方可以得到不同的类/方法中注解的各种参数与值。 从JDK5开始&#xff…...

《STL源码剖析》理解之将类成员函数和for_each等算法结合

类成员函数可以通过函数适配器(function adapters)包装成一个仿函数(重载了operator()的类)&#xff0c;将其搭配于STL算法一起使用。#include <algorithm> #include <functional> #include <vector> #include <iostream>using namespace std;class In…...

如何构建应用标准化体系

标准化的过程实际上就是对运维对象的识别和建模过程。形成统一的对象模型后&#xff0c;各方在统一的认识下展开有效协作&#xff0c;然后针对不同的运维对象&#xff0c;再抽取出它们所对应的运维场景&#xff0c;接下来才是运维场景的自动化实现。 在标准化的过程中&#xf…...

【RabbitMQ笔记03】消息队列RabbitMQ七种模式之WorkQueues工作队列模式

这篇文章&#xff0c;主要介绍消息队列RabbitMQ七种模式之WorkQueues工作队列模式。 目录 一、工作队列模式 1.1、什么是Work Queues模式 1.2、工作队列模式的使用 &#xff08;1&#xff09;引入依赖 &#xff08;2&#xff09;编写生产者 &#xff08;3&#xff09;编写…...

认识html

1.html的特点先看一段简单的html代码<html><head></head><body>hello world</body> </html>如果将这段带有这段代码的.html文件拉进浏览器中,就会出现一个页面,内容就是hello world,如下图:由上面的代码,我们可以了解到一些html代码的特点…...

在外包公司熬了 3 年终于进了字节,竭尽全力....

其实两年前校招的时候就往字节投了一次简历&#xff0c;结果很明显凉了&#xff0c;随后这个理想就被暂时放下了&#xff0c;但是这个种子一直埋在心里这两年除了工作以外&#xff0c;也会坚持写博客&#xff0c;也因此结识了很多优秀的小伙伴&#xff0c;从他们身上学到了特别…...

绝对让你明明白白,脚把脚带你盯着 I2C 时序图将 I2C 程序给扣出来(基于STM32的模拟I2C)

目录前言一、关于STM32 I/O端口位的基本结构讲解二、模拟I2C编写前的需知道的知识1、I2C简介2、根据时序编写模拟I2C程序重要的两点Ⅰ、主机发送数据给从机时的时序控制Ⅱ、主机接收来自从机的数据时的时序控制Ⅲ、完整的I2C时序图&#xff08;按写程序的思想分割时序&#xff…...

2023年全国最新工会考试精选真题及答案5

百分百题库提供工会考试试题、工会考试预测题、工会考试真题、工会证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 一、单选题 1.企业工会委员会实行&#xff08;&#xff09;&#xff0c;重要问题须经&#xff08;&#x…...

一文2000字手把手教你自动化测试Selenium+pytest+数据驱动

主流自动化框架 selenium &#xff1a;web端自动化框架 &#xff0c;&#xff08;行业里面最核心的框架&#xff09; appium &#xff1a;手机app端框架 requests &#xff1a;接口测试 selenium 工具类封装 selenium提供了很多方法供我们去完成网页元素的操作&#xff0c; …...

windows安装Ubuntu子系统以及图形化界面记录

文章目录1. windows环境设置2. 开始安装3. ubuntu使用3.1 启动和退出 Linux 子系统3.2 安装位置3.3 更换源4. 安装图形化界面4.1 安装VcXsrv4.2 安装桌面环境&#xff08;1&#xff09;方法1&#xff1a;VcXsrv Gnome&#xff08;2&#xff09;方法2&#xff1a;VcXsrv Xfce4…...

通俗易懂,十分钟读懂DES,详解DES加密算法原理,DES攻击手段以及3DES原理。Python DES实现源码

文章目录1、什么是DES2、DES的基本概念3、DES的加密流程4、DES算法步骤详解4.1 初始置换(Initial Permutation&#xff0c;IP置换)4.2 加密轮次4.3 F轮函数4.3.1 拓展R到48位4.3.2 子密钥K的生成4.3.3 当前轮次的子密钥与拓展的48位R进行异或运算4.3.4 S盒替换&#xff08;Subs…...

为多态基类声明virtual析构函数

我们知道&#xff0c;有时会让一个基类指针指向用 new 运算符动态生成的派生类对象&#xff08;类似接口的作用&#xff09;&#xff1b;同时&#xff0c;用 new 运算符动态生成的对象都是通过 delete 指向它的指针来释放的。如果一个基类指针指向用 new 运算符动态生成的派生类…...

啊哈 算法读书笔记 第 2 章 栈、队列、链表

第 2 章 栈、队列、链表 目录 第 2 章 栈、队列、链表 队列&#xff1a; 解密回文——栈 纸牌游戏&#xff1a; 链表 模拟链表 队列&#xff1a; 首先将第 1 个数删除&#xff0c;紧接着将第 2 个数放到这串数的末尾&#xff0c;再将第 3 个数删除并将第 4 个数放到这串…...

Git ---- IDEA 集成 Git

Git ---- IDEA 集成 Git1. 配置 Git 忽略文件2. 定位 Git 程序3. 初始化本地库4. 添加到暂存区5. 提交到本地库6. 切换版本7. 创建分支8. 切换分支9. 合并分支10. 解决冲突1. 配置 Git 忽略文件 1. Eclipse 特定文件 2. IDEA 特定文件 3. Maven 工程的 target 目录 问题1…...

【LeetCode 704】【Go】二分查找

二分查找题解 一、碎碎念 从本周开始&#xff0c;重新更新刷题记录了哈。 基于费曼学习法的原理&#xff0c;最好的输入是输出&#xff0c;所以与大家分享。 鉴于目前这个糟糕的市场环境&#xff0c;还是要练好自己的基本技术&#xff0c;万一那天就被迫 N 1了&#xff0c;你…...

【代码随想录训练营】【Day23】第六章|二叉树|669. 修剪二叉搜索树 |108.将有序数组转换为二叉搜索树|538.把二叉搜索树转换为累加树

修剪二叉搜索树 题目详细&#xff1a;LeetCode.669 做这道题之前建议先看视频讲解&#xff0c;没有想象中那么复杂&#xff1a;代码随想录—修剪二叉搜索树 由题可知&#xff0c;需要删除节点值不在区间内的节点&#xff0c;所以可以得到三种情况&#xff1a; 情况一&#…...

CV——day78 读论文:通过静态背景构建扩展低通道路边雷达的探测距离(目标是规避风险)

Extending the Detection Range for Low-Channel Roadside LiDAR by Static Background Construction 通过静态背景构建扩展低通道路边雷达的探测距离I. INTRODUCTIONII. RELATED WORKA. LiDAR-Based 3-D Vehicle and Road User DetectionB. LiDAR Data Background FilteringC.…...

【编程入门】应用市场(go语言版)

背景 前面已输出多个系列&#xff1a; 《十余种编程语言做个计算器》 《十余种编程语言写2048小游戏》 《17种编程语言10种排序算法》 《十余种编程语言写博客系统》 《十余种编程语言写云笔记》 《N种编程语言做个记事本》 目标 为编程初学者打造入门学习项目&#xff0c;使…...

Linux(openEuler)没有界面连接互联网方法

前言: 系统版本openEuleropenEuler-22.03-LTS-x86_64-dvd 我们在安装linux之后&#xff0c;一般都是无界面的情况。大部分情况都是需要自己安装界面的&#xff0c;如果路由器的情况下直接插上网络就好了。下面就开始介绍两种方法进行linxu网络的连接。 注意: 小编是使用的第一…...

第一天 软考中级--嵌入式系统设计师考试复习教程开始了

第一天 嵌入式系统设计师考试复习教程 第二天 软考中级--嵌入式系统设计师考试考试大纲解析 目录...

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

docker详细操作--未完待续

docker介绍 docker官网: Docker&#xff1a;加速容器应用程序开发 harbor官网&#xff1a;Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台&#xff0c;用于将应用程序及其依赖项&#xff08;如库、运行时环…...

工业安全零事故的智能守护者:一体化AI智能安防平台

前言&#xff1a; 通过AI视觉技术&#xff0c;为船厂提供全面的安全监控解决方案&#xff0c;涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面&#xff0c;能够实现对应负责人反馈机制&#xff0c;并最终实现数据的统计报表。提升船厂…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序

一、开发准备 ​​环境搭建​​&#xff1a; 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 ​​项目创建​​&#xff1a; File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...