【JAVA】注解+元注解+自定义注解(万字详解)

article/2025/7/6 15:14:13

608564A16E7D652E882914E830EE4050(1)

📚博客主页:代码探秘者

✨专栏:《JavaSe》 其他更新ing…

❤️感谢大家点赞👍🏻收藏⭐评论✍🏻,您的三连就是我持续更新的动力❤️

🙏作者水平有限,欢迎各位大佬指点,相互学习进步!


img

文章目录

    • 一、Java 注解基础
      • 1.1 什么是注解(Annotation)?
      • 1.2 注解的分类
    • 二、常用内置注解
      • 2.1 编译器相关注解(来自 `java.lang`)
      • 2.2 元注解(用于定义注解)
    • 三、自定义注解基础
      • 3.1 自定义注解的基本结构
      • 3.2 使用自定义注解
      • 3.3 注解解析(通过反射读取)
    • 四、注解的常见应用场景(了解)
      • 4.1 Spring 框架注解
      • 4.2 JUnit 单元测试注解
      • 4.3 Lombok 注解(简化开发)
    • 五、实战:自定义注解
      • 5.1 判断手机号格式-@IsMobile
      • 5.2 正则表达式:校验
      • 5.3 校验类实现
      • 5.4 使用示例
    • 六、实战:结合 AOP 使用注解实现功能(了解)
      • 6. 1 日志切面-@LogExecutionTime
      • 6.2 自定义注解 @AutoFill+AOP
        • 6.2.1 步骤1
        • 6.2.3 步骤2
        • 6.2.3 步骤3

本文章是对 Java 注解(Annotation)相关内容的系统总结,包括基础知识、常见内置注解、自定义注解、注解的运行机制,以及在实际开发中的常见应用场景。


一、Java 注解基础

1.1 什么是注解(Annotation)?

注解是 Java 5 引入的一种元数据机制,用于修饰代码(类、方法、字段、参数等),不会直接影响代码运行逻辑,但可以被编译器或运行时工具读取,并执行相关处理。

1.2 注解的分类

按生命周期可分为:

  • SOURCE:只在源码中保留,编译后丢弃,如 @Override
  • CLASS:保留到 class 文件中,运行时不可见(默认)
  • RUNTIME:运行时可通过反射获取,常用于框架开发(如 Spring)

二、常用内置注解

2.1 编译器相关注解(来自 java.lang

注解作用说明
@Override标明方法重写父类方法
@Deprecated表示方法/类已过时
@SuppressWarnings抑制编译器警告,例如 unchecked

2.2 元注解(用于定义注解)

注解含义
@Target指定注解可修饰的目标(类、方法、字段等)
@Retention指定注解的保留策略(SOURCE、CLASS、RUNTIME)
@Documented指定注解是否包含在 Javadoc 中
@Inherited指定子类是否能继承父类的注解

三、自定义注解基础

3.1 自定义注解的基本结构

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {String value() default "default";
}

3.2 使用自定义注解

public class Service {@MyLog("执行业务方法")public void doSomething() {System.out.println("正在处理...");}
}

3.3 注解解析(通过反射读取)

Method method = Service.class.getMethod("doSomething");
MyLog log = method.getAnnotation(MyLog.class);
if (log != null) {System.out.println("注解值:" + log.value());
}

四、注解的常见应用场景(了解)

4.1 Spring 框架注解

注解用途
@Component注册为 Spring Bean
@Service标识服务层组件
@Autowired自动注入依赖
@RequestMapping映射 Web 请求
@RestController标记为 REST 控制器
@Transactional事务管理

4.2 JUnit 单元测试注解

注解用途
@Test表示一个测试方法
@Before每个测试前执行
@After每个测试后执行
@BeforeClass所有测试前执行一次

4.3 Lombok 注解(简化开发)

注解功能
@Getter自动生成 getter 方法
@Setter自动生成 setter 方法
@Data包括 getter、setter、toString 等
@Builder生成建造者模式代码

五、实战:自定义注解

5.1 判断手机号格式-@IsMobile

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented                 //注解规则
@Constraint(validatedBy = {IsMobileValidator.class})
public @interface IsMobile {String message() default "手机号码格式错误";boolean required() default true;Class<?>[] groups() default { };//默认参数Class<? extends Payload>[] payload() default { };//默认参数
}

✅ 解析

内容含义
@IsMobile自定义注解名称,用于手机号验证
@Target(...)注解可以放的位置
@Retention(RUNTIME)保留到运行时
@Constraint(...)指定由哪个类执行校验逻辑
message()提示信息
required()是否必须有值(用于支持“非必填但格式正确”)
groups()/payload()扩展字段,兼容 Bean Validation 框架

5.2 正则表达式:校验

 * ValidatorUtil: 完成一些校验工作,比如手机号码格式是否正确..* 提醒:java基础时,我们讲过正则表达式的使用,可以回顾.*/
public class ValidatorUtil {//校验手机号码的正则表达式//13300000000 合格//11000000000 不合格private static final Pattern mobile_pattern = Pattern.compile("^[1][3-9][0-9]{9}$");//编写方法, 如果满足规则,返回T, 否则返回Fpublic static boolean isMobile(String mobile) {if(!StringUtils.hasText(mobile)) {return false;}//进行正则表达式校验-java基础讲过Matcher matcher = mobile_pattern.matcher(mobile);return matcher.matches();}//测试一下校验方法@Testpublic void t1() {String mobile = "13300000009";System.out.println(isMobile(mobile));//}
}

5.3 校验类实现

//我们自拟定注解 IsMobile   的校验规则, 可以自己根据业务需求来编写..
public class IsMobileValidator          //注解implements ConstraintValidator<IsMobile, String> {private boolean required = false;//初始化方法@Overridepublic void initialize(IsMobile constraintAnnotation) {//初始化required = constraintAnnotation.required();}@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {//必填if (required) {return ValidatorUtil.isMobile(value);	//这里是调用正则表达式} else {//非必填if (!StringUtils.hasText(value)) {return true;} else {return ValidatorUtil.isMobile(value);}}}
}

5.4 使用示例

public class UserDTO {@IsMobile(message = "手机号格式不正确", required = true)private String phone;// 其他字段...
}

六、实战:结合 AOP 使用注解实现功能(了解)

6. 1 日志切面-@LogExecutionTime

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {}@Aspect
@Component
public class LoggingAspect {@Around("@annotation(LogExecutionTime)")public Object logTime(ProceedingJoinPoint joinPoint) throws Throwable {long start = System.currentTimeMillis();Object proceed = joinPoint.proceed();long duration = System.currentTimeMillis() - start;System.out.println("执行耗时:" + duration + "ms");return proceed;}
}

6.2 自定义注解 @AutoFill+AOP

@AutoFill 主要是进行公共字段填充

  • 下面案例来自苍穹外卖项目,提供学习了解
6.2.1 步骤1
/*** 自定义注解,用于标识某个方法需要进行功能字段自动填充处理*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {//数据库操作类型:UPDATE INSERTOperationType value();
}

其中OperationType已在sky-common模块中定义

/*** 数据库操作类型*/
public enum OperationType {/*** 更新操作*/UPDATE,/*** 插入操作*/INSERT
}
6.2.3 步骤2

自定义切面 AutoFillAspect

在sky-server模块,创建com.sky.aspect包。

package com.sky.aspect;/*** 自定义切面,实现公共字段自动填充处理逻辑*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {/*** 切入点*/@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")public void autoFillPointCut(){}/*** 前置通知,在通知中进行公共字段的赋值*/@Before("autoFillPointCut()")public void autoFill(JoinPoint joinPoint){/重要//可先进行调试,是否能进入该方法 提前在mapper方法添加AutoFill注解log.info("开始进行公共字段自动填充...");}
}

完善自定义切面 AutoFillAspect 的 autoFill 方法

/*** 自定义切面,实现公共字段自动填充处理逻辑*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {/*** 切入点*/@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")public void autoFillPointCut(){}/*** 前置通知,在通知中进行公共字段的赋值*/@Before("autoFillPointCut()")public void autoFill(JoinPoint joinPoint){log.info("开始进行公共字段自动填充...");//获取到当前被拦截的方法上的数据库操作类型MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象OperationType operationType = autoFill.value();//获得数据库操作类型//获取到当前被拦截的方法的参数--实体对象Object[] args = joinPoint.getArgs();if(args == null || args.length == 0){return;}Object entity = args[0];//准备赋值的数据LocalDateTime now = LocalDateTime.now();Long currentId = BaseContext.getCurrentId();//根据当前不同的操作类型,为对应的属性通过反射来赋值if(operationType == OperationType.INSERT){//为4个公共字段赋值try {Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);//通过反射为对象属性赋值setCreateTime.invoke(entity,now);setCreateUser.invoke(entity,currentId);setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);} catch (Exception e) {e.printStackTrace();}}else if(operationType == OperationType.UPDATE){//为2个公共字段赋值try {Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);//通过反射为对象属性赋值setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);} catch (Exception e) {e.printStackTrace();}}}
}
6.2.3 步骤3

在Mapper接口的方法上加入 AutoFill 注解

CategoryMapper为例,分别在新增和修改方法添加@AutoFill()注解,也需要EmployeeMapper做相同操作

package com.sky.mapper;@Mapper
public interface CategoryMapper {/*** 插入数据* @param category*/@Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +" VALUES" +" (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")@AutoFill(value = OperationType.INSERT)void insert(Category category);/*** 根据id修改分类* @param category*/@AutoFill(value = OperationType.UPDATE)void update(Category category);}

同时,将业务层为公共字段赋值的代码注释掉。

1). 将员工管理的新增和编辑方法中的公共字段赋值的代码注释。

2). 将菜品分类管理的新增和修改方法中的公共字段赋值的代码注释。


http://www.hkcw.cn/article/JtnmXieFYU.shtml

相关文章

unity随机生成未知符号教程

目录 前言方法1方法2脚本后言示例代码 前言 在某些游戏中&#xff0c;有一些让人感到意味不明的未知符号&#xff0c;例如在游戏《巴别塔圣歌》中&#xff0c;就有这样一些能让人在初次就看不懂的未知符号。 或者在其他时候&#xff0c;这些未知符号如果跟粒子系统结合在一起的…

OpenCV——Mac系统搭建OpenCV的Java环境

这里写目录标题 一、源码编译安装1.1、下载源码包1.2、cmake安装1.3、java配置1.4、测试 二、Maven引入2.1、添加Maven依赖2.2、加载本地库 一、源码编译安装 1.1、下载源码包 官网下载opencv包&#xff1a;https://opencv.org/releases/ 以4.6.0为例&#xff0c;下载解压后&…

从Docker拉取镜像一直失败超时解决办法

项目场景&#xff1a; 在ubuntu中&#xff0c;使用docker拉去镜像时&#xff0c;一直超时&#xff0c;拉去失败。 问题描述 原因分析&#xff1a; 国外服务器网络不好导致。 解决方案&#xff1a; 解决方案1 设置国内源 我这边测试&#xff0c;更改以后仍然失败 阿里云提供…

告别printf!嵌入式系统高效日志记录方案

目录 1、分级控制与动态过滤机制 2、异步处理与零拷贝架构 3、跨平台适配层设计 在嵌入式系统开发领域&#xff0c;日志记录系统如同数字世界的黑匣子&#xff0c;承载着系统运行状态的关键信息。传统的printf调试方式虽简单易用&#xff0c;但在处理复杂系统时暴露出效率低…

复变函数 $w = z^2$ 的映射图像演示

复变函数 w z 2 w z^2 wz2 的映射图像演示 复变函数 w z 2 w z^2 wz2 是一个基本的二次函数&#xff0c;在复平面上具有有趣的映射性质。下面我将介绍这个函数的映射特性&#xff0c;并使用MATLAB进行可视化演示。 映射特性 极坐标表示&#xff1a;若 z r e i θ z …

【Redis】Zset 有序集合

文章目录 常用命令zaddzcardzcountzrange && zrevrangezrangebyscorezpopmax && bzpopmaxzpopmin && zpopmaxzrank && zrevrankzscorezremzremrangebyrankzremrangebyscorezincrby 集合间操作交集 zinterstore并集 zunionstore 内部编码应用场…

【AI论文】视觉语言模型中的自我修正推理

摘要&#xff1a;推理视觉语言模型&#xff08;VLMs&#xff09;在复杂的多模态任务上表现出了良好的性能。 然而&#xff0c;它们仍然面临着重大挑战&#xff1a;它们对推理错误高度敏感&#xff0c;需要大量带注释的数据或精确的验证器&#xff0c;并且难以在特定领域之外进行…

正则表达式在Java中的应用(补充)

正则表达式在Java中的应用 Java通过java.util.regex包提供正则表达式支持&#xff0c;核心类包括Pattern和Matcher。Pattern用于编译正则表达式模式&#xff0c;Matcher用于匹配操作。基本语法遵循标准正则规则&#xff0c;如\d匹配数字&#xff0c;\w匹配单词字符。 Pattern…

C++ 内存泄漏检测器设计

文章目录 1. C中的动态内存分配2. 什么是内存泄漏3. 内存泄漏的代码案例4. 内存泄漏检查器的设计模块1&#xff1a;位置信息捕获&#xff1a;模块2&#xff1a;内存分配跟踪&#xff1a;模块3&#xff1a;内存释放跟踪&#xff1a;模块4&#xff1a;泄漏记录存储&#xff1a;模…

线程安全与线程池

概念&#xff1a;多个线程&#xff0c;同时操作同一个共享资源的时候&#xff0c;可能会出现业务安全问题。 出现线程安全问题的条件&#xff0c;原因&#xff1a;1.存在多个线程在同时执行 2.同时访问一个共享资源 3.存在修改该共享资源 线程同步&#xff1a;是线程安全…

网络安全的学习路线是怎么样的?

我是几乎完全自学的&#xff0c;十年前从双非跨专业考研到中科大软件学院网络安全专业&#xff0c;读研之前&#xff0c;C语言是自学的&#xff0c;数据结构是自学的&#xff0c;计算机网络是自学的&#xff0c;操作系统是自学的&#xff0c;微机原理是自学的。为了让我们能跟上…

每日算法-250602

每日算法学习记录 - 250602 今天学习和复习了两道利用前缀和与哈希表解决的子数组问题&#xff0c;特此记录。 560. 和为 K 的子数组 题目 思路 本题的核心思想是利用 前缀和 与 哈希表 来优化查找过程。 解题过程 题目要求统计和为 k 的子数组个数。 我们首先预处理出一…

Hadoop学习笔记

&#xff08;1&#xff09;Hadoop概述 Hadoop是一个开源的分布式计算和存储框架&#xff0c;用于处理大规模数据集&#xff08;大数据&#xff09;的并行处理。它由Apache基金会开发&#xff0c;核心设计灵感来自Google的MapReduce和Google文件系统&#xff08;GFS&#xff09…

PCIe—TS1/TS2 之Polling.Configuration (二)

前文 在 Polling.Configuration 次状态中&#xff0c;发送⽅停⽌发送 TS1 序列&#xff0c;转⽽发送 TS2 序列&#xff0c;TS2 序列中的链路和通道&#xff08;lane&#xff09;字段仍然使⽤填充字段填充。 该状态中&#xff0c;发送⽅转⽽发送 TS2 的⽬的是通知链路对端的设备…

如何增加 cPanel中的 PHP 最大上传大小?

PHP通过限制文件上传大小来保护服务器性能&#xff0c;但默认限制对于许多现代网页应用来说太低了。当PHP应用程序显示错误信息&#xff0c;要求你增加PHP的最大上传文件大小时&#xff0c;你可能会遇到这个问题。有多种方法可以提高上传限制&#xff0c;包括直接编辑PHP配置文…

linux——文件系统

被打开的文件放到内存中没有被打开的文件放到磁盘 1. 硬件-->磁盘 磁盘的存储基本单位&#xff1a;扇区&#xff08;512字节&#xff09; 512字节写入到磁盘&#xff0c;磁盘如何转动&#xff1a; 磁盘写入的时候是向柱面进行批量写入的 CHS地址&#xff1a;cylind heade…

HBM的那些事2 写操作

搞懂写&#xff0c;把下面这幅图搞定&#xff0c;基本上就掌握了7成了。 术语解释 WL &#xff1a; write latency&#xff0c;说的是命令到发送数据的WDQS的间隔&#xff0c;注意这里不包含twpre1的时间&#xff0c;通过配置MR1实现。 twpre1: 在发送写数据之前&#xff0c;W…

B1039 PAT乙级JAVA题解 到底买不买

小红想买些珠子做一串自己喜欢的珠串。卖珠子的摊主有很多串五颜六色的珠串&#xff0c;但是不肯把任何一串拆散了卖。于是小红要你帮忙判断一下&#xff0c;某串珠子里是否包含了全部自己想要的珠子&#xff1f;如果是&#xff0c;那么告诉她有多少多余的珠子&#xff1b;如果…

力扣HOT100之多维动态规划:62. 不同路径

这道题用二维dp数组来做相当简单&#xff0c;是一道入门题。直接上动规五部曲&#xff1a; 1.确定dp[i][j]的含义&#xff1a;从起点到位置为[i][j]处的路径总数 2.确定递推公式 dp[i][j] dp[i - 1][j] dp[i][j - 1]; 3.dp数组初始化 dp[0][j] 1;dp[i][0] 1; 4.确定遍历顺序…

css呼吸灯

效果图 只是简单的呼吸效果&#xff0c;您按照需求自己拓展即可。 代码 keyframes light{from{opacity: 1;}to{opacity: 0.2;}}使用 .view{animation-name: light;animation-duration: 1s;animation-timing-function: linear;animation-iteration-count: infinite;animation-…