注解原理剖析与实战
一、注解及其原理
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.注解的基本概念 注解,可以看作是对 一个类/方法的一个扩展的模版,每个类/方法按照注解类中的规则,来为类/方法注解不同的参数,在用到的地方可以得到不同的类/方法中注解的各种参数与值。 从JDK5开始ÿ…...

《STL源码剖析》理解之将类成员函数和for_each等算法结合
类成员函数可以通过函数适配器(function adapters)包装成一个仿函数(重载了operator()的类),将其搭配于STL算法一起使用。#include <algorithm> #include <functional> #include <vector> #include <iostream>using namespace std;class In…...

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

【RabbitMQ笔记03】消息队列RabbitMQ七种模式之WorkQueues工作队列模式
这篇文章,主要介绍消息队列RabbitMQ七种模式之WorkQueues工作队列模式。 目录 一、工作队列模式 1.1、什么是Work Queues模式 1.2、工作队列模式的使用 (1)引入依赖 (2)编写生产者 (3)编写…...

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

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

绝对让你明明白白,脚把脚带你盯着 I2C 时序图将 I2C 程序给扣出来(基于STM32的模拟I2C)
目录前言一、关于STM32 I/O端口位的基本结构讲解二、模拟I2C编写前的需知道的知识1、I2C简介2、根据时序编写模拟I2C程序重要的两点Ⅰ、主机发送数据给从机时的时序控制Ⅱ、主机接收来自从机的数据时的时序控制Ⅲ、完整的I2C时序图(按写程序的思想分割时序ÿ…...

2023年全国最新工会考试精选真题及答案5
百分百题库提供工会考试试题、工会考试预测题、工会考试真题、工会证考试题库等,提供在线做题刷题,在线模拟考试,助你考试轻松过关。 一、单选题 1.企业工会委员会实行(),重要问题须经(&#x…...

一文2000字手把手教你自动化测试Selenium+pytest+数据驱动
主流自动化框架 selenium :web端自动化框架 ,(行业里面最核心的框架) appium :手机app端框架 requests :接口测试 selenium 工具类封装 selenium提供了很多方法供我们去完成网页元素的操作, …...

windows安装Ubuntu子系统以及图形化界面记录
文章目录1. windows环境设置2. 开始安装3. ubuntu使用3.1 启动和退出 Linux 子系统3.2 安装位置3.3 更换源4. 安装图形化界面4.1 安装VcXsrv4.2 安装桌面环境(1)方法1:VcXsrv Gnome(2)方法2:VcXsrv Xfce4…...

通俗易懂,十分钟读懂DES,详解DES加密算法原理,DES攻击手段以及3DES原理。Python DES实现源码
文章目录1、什么是DES2、DES的基本概念3、DES的加密流程4、DES算法步骤详解4.1 初始置换(Initial Permutation,IP置换)4.2 加密轮次4.3 F轮函数4.3.1 拓展R到48位4.3.2 子密钥K的生成4.3.3 当前轮次的子密钥与拓展的48位R进行异或运算4.3.4 S盒替换(Subs…...

为多态基类声明virtual析构函数
我们知道,有时会让一个基类指针指向用 new 运算符动态生成的派生类对象(类似接口的作用);同时,用 new 运算符动态生成的对象都是通过 delete 指向它的指针来释放的。如果一个基类指针指向用 new 运算符动态生成的派生类…...

啊哈 算法读书笔记 第 2 章 栈、队列、链表
第 2 章 栈、队列、链表 目录 第 2 章 栈、队列、链表 队列: 解密回文——栈 纸牌游戏: 链表 模拟链表 队列: 首先将第 1 个数删除,紧接着将第 2 个数放到这串数的末尾,再将第 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】二分查找
二分查找题解 一、碎碎念 从本周开始,重新更新刷题记录了哈。 基于费曼学习法的原理,最好的输入是输出,所以与大家分享。 鉴于目前这个糟糕的市场环境,还是要练好自己的基本技术,万一那天就被迫 N 1了,你…...

【代码随想录训练营】【Day23】第六章|二叉树|669. 修剪二叉搜索树 |108.将有序数组转换为二叉搜索树|538.把二叉搜索树转换为累加树
修剪二叉搜索树 题目详细:LeetCode.669 做这道题之前建议先看视频讲解,没有想象中那么复杂:代码随想录—修剪二叉搜索树 由题可知,需要删除节点值不在区间内的节点,所以可以得到三种情况: 情况一&#…...

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语言版)
背景 前面已输出多个系列: 《十余种编程语言做个计算器》 《十余种编程语言写2048小游戏》 《17种编程语言10种排序算法》 《十余种编程语言写博客系统》 《十余种编程语言写云笔记》 《N种编程语言做个记事本》 目标 为编程初学者打造入门学习项目,使…...

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

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

分享 10 个高频 Python 面试题
Python 很容易学会,但很难掌握。你可以在几天内了解它的基本语法,但是要能够用 Python 开发出足够好的商业软件,多年的实践是必须的。因为,无论你使用哪种编程语言,你都必须对其复杂的内部机制有足够的了解,…...

ThreadLocal原理、结构、源码解析
文章目录一、Thread简介1.什么是ThreadLocal2.为什么要是用ThreadLocal2.1Synchronized、Lock保证线程安全2.2ThreadLocal保证线程安全3.ThreadLocal和Synchronized的区别二、ThreadLocal原理1.Thread抽象内部结构2.ThreadLocal源码2.1Thread、ThreadLocal、ThreadLocalMap、En…...

分布式之PBFT算法
写在前面 在分布式之拜占庭问题 一文中我们分析了拜占庭问题,并一起看了支持拜占庭容错的口信消息性和签名消息性算法,但是这两种算法都有一个非常严重的问题,就是消息数量太多,通信的成本太大,消息数量复杂度为O(n ^…...

Linux 操作系统——查看/修改系统时区、时间、本地时间修改为UTC
文章目录1.背景描述2.知识储备3.解决步骤1. 查看当前时区2.修改设置Linux服务器时区3.复制相应的时区文件,替换系统时区文件;或者创建链接文件4. 查看和修改Linux的时间5. 硬件时间和系统时间的 相互同步1.背景描述 最近一个项目日期采用java8的LocalDa…...

CSS数据类型以及符号
css数据类型定义的是css属性中具有代表性的值,在规范的语法格式中,使用关键字外加一对 <和>表示,例如数值类型<number>、色值类型<color>等。 举个例子:background-image这个css属性语法结构如下: …...

LeetCode-54. 螺旋矩阵
题目来源 54. 螺旋矩阵 题目思路 while循环只遍历"环",不成环就不遍历了 四个边界 上边界 top : 0下边界 bottom : matrix.length - 1左边界 left : 0右边界 right : matrix[0].length - 1 矩阵不一定是方阵 top < bottom && left < r…...

【Python入门第十八天】Python For 循环
Python For 循环 for 循环用于迭代序列(即列表,元组,字典,集合或字符串)。 这与其他编程语言中的 for 关键字不太相似,而是更像其他面向对象编程语言中的迭代器方法。 通过使用 for 循环,我们…...

Qt图片定时滚动播放器
目录参考结构PicturePlay.promain.cpppictureplay.hpictureplay.cpppictureplay.ui效果源码参考 Qt图片浏览器 QT制作一个图片播放器 Qt中自适应的labelpixmap充满窗口后,无法缩小只能放大 可以显示jpg、jpeg、png、bmp。可以从电脑上拖动图到窗口并显示出来或者打开…...

李宏毅2023春季机器学习课程
目录2021&2022课程重磅须知我维护的其他项目更新日志课程地址课程资料直链课程作业直链其他优质课程2021&2022课程 CSDN Github 重磅须知 为方便所有网课资料与优质电子书籍的实时更新维护,创建一个在线实时网盘文件夹; 网盘获取方式&#…...

计算机操作系统知识点汇总
计算机操作系统选择填空题,300知识点,包含操作系统概论、处理机管理、内存管理、设备管理、文件管理等,为大学生期末创造奇迹提供无限可能 1、填空题 1、操作系统是对计算机资源进行管理的软件 2、操作系统是提供了处理机管理、 存储器管理…...