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

news2025/6/5 3:45:56

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, 否则返回F
    public static boolean isMobile(String mobile) {
        if(!StringUtils.hasText(mobile)) {
            return false;
        }

        //进行正则表达式校验-java基础讲过
        Matcher matcher = mobile_pattern.matcher(mobile);
        return matcher.matches();
    }

    //测试一下校验方法
    @Test
    public void t1() {
        String mobile = "13300000009";
        System.out.println(isMobile(mobile));//
    }
}

5.3 校验类实现

//我们自拟定注解 IsMobile   的校验规则, 可以自己根据业务需求来编写..
public class IsMobileValidator          //注解
        implements ConstraintValidator<IsMobile, String> {
    private boolean required = false;
    //初始化方法
    @Override
    public void initialize(IsMobile constraintAnnotation) {
        //初始化
        required = constraintAnnotation.required();
    }

    @Override
    public 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 INSERT
    OperationType 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.coloradmin.cn/o/2396885.html

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

相关文章

【Doris基础】Apache Doris中的Version概念解析:深入理解数据版本管理机制

目录 引言 1 Version概念基础 1.1 什么是Version 1.2 Version的核心作用 1.3 Version相关核心概念 2 Version工作机制详解 2.1 Version在数据写入流程中的作用 2.2 Version在数据查询流程中的作用 2.3 Version的存储结构 3 Version的进阶特性 3.1 Version的合并与压…

【图像处理基石】如何进行图像畸变校正?

图像畸变校正常用于计算机视觉、摄影测量学和机器人导航等领域&#xff0c;能够修正因镜头光学特性或传感器排列问题导致的图像失真。下面我将介绍几种常用的图像畸变校正算法&#xff0c;并提供Python实现和测试用例。 常用算法及Python实现 1. 径向畸变校正 径向畸变是最常…

电力系统时间同步系统

电力系统中&#xff0c;电压、电流、功率变化等特征量测量都是时间相关函数[1]&#xff0c;统一精准的时间源对于电网安全稳定运行至关重要&#xff0c;因此&#xff0c;电力系统运行规程[2]中明确要求继电保护装置、自动化装置、安全稳定控制系统、能量管理系统和生产信息管理…

Vue使用toFixed保留两位小数的三种写法

第一种&#xff1a;直接写在js里面&#xff0c;这是最简单的 val.toFixed(2)第二种&#xff1a;在ElementUi表格中使用 第三种&#xff1a;在取值符号中使用 {{}} 定义一个方法 towNumber(val) { return val.toFixed(2) } 使用 {{ towNumber(row.equiV…

Arch安装botw-save-state

devkitPro https://blog.csdn.net/qq_39942341/article/details/148387077?spm1001.2014.3001.5501 cargo https://blog.csdn.net/qq_39942341/article/details/148387783?spm1001.2014.3001.5501 megaton https://blog.csdn.net/qq_39942341/article/details/148388164?spm…

电脑为什么换个ip就上不了网了

在日常使用电脑上网时&#xff0c;很多人可能遇到过这样的问题&#xff1a;当IP地址发生变化后&#xff0c;突然就无法连接网络了。当电脑更换IP地址后无法上网&#xff0c;这一现象可能由多种因素导致&#xff0c;涉及网络配置、硬件限制或运营商策略等层面。以下是系统性分析…

github 2FA双重认证丢失解决

文章目录 前言一. 凭借ssh 解锁步骤1.1 要求输入设备码1.2.进入二重验证界面1.3.开始2FA恢复1.4.选择使用ssh验证 二.预防措施2.1 云盘上传git_recover_codes.txt2.2 开启多源FA认证2.2.1 大陆无法使用手机验证码 三.参考资料 前言 场景&#xff1a;没有意识到github recovery …

linux驱动 - 5: simple usb device驱动

参考第2节, 准备好编译环境并实现hello.ko: linux驱动 - 2: helloworld.ko_linux 驱动开发 hello world ko-CSDN博客 下面在hello模块的基础上, 添加代码, 实现一个usb设备驱动的最小骨架. #include <linux/init.h> #include <linux/module.h> #include <lin…

ETL脚本节点使用的方式

随着大数据时代的到来&#xff0c;企业对数据处理的需求日益增长&#xff0c;ETL 作为数据整合的关键技术&#xff0c;逐渐走进我们的视野。本文将为您揭秘 ETL 脚本节点的使用方式&#xff0c;助您轻松驾驭数据处理新境界。 一、ETL脚本的优势 1.提高效率&#xff1a;ETL 脚…

PH热榜 | 2025-06-02

1. Circuit Tracer 标语&#xff1a;Anthropic的开放工具&#xff1a;让我们了解AI是如何思考的 介绍&#xff1a;Anthropic的开源工具Circuit Tracer可以帮助研究人员理解大型语言模型&#xff08;LLMs&#xff09;&#xff0c;它通过将内部计算可视化为归因图的方式展现相关…

: influxdb + grafana+JMeter

influxdb和Grafana 不安装在被测机器上&#xff0c;可以统一放到一台机器上面 1、influxdb&#xff1a;一种时序数据库&#xff0c; 可以永久性保存数据【除非手动清除和数据库坏了】 2、Grafana&#xff1a;grafana是一款用go编写的开源应用&#xff0c;用于大规模指标数据的可…

TDengine 基于 TDgpt 的 AI 应用实战

基于 TDgpt 时序数据智能体的风力发电预测 作者&#xff1a; derekchen Demo 数据集准备 我们使用公开的UTSD数据集里面的某风场发电数据&#xff0c;作为预测算法的数据来源&#xff0c;基于历史数据预测未来一天内的每15分钟的发电量。原始数据集的采集频次为4秒&#xff…

RocketMQ 消息发送核心源码解析:DefaultMQProducerImpl.send () 方法深度剖析

引言 在分布式系统中&#xff0c;消息队列是实现异步通信、服务解耦和流量削峰的关键组件。Apache RocketMQ 作为一款高性能、高可靠的消息中间件&#xff0c;被广泛应用于各类互联网场景。其中&#xff0c;消息发送是最基础也是最重要的功能之一。本文将深入剖析 RocketMQ 中…

BiliNote部署实践

​ 开源地址&#xff1a; https://github.com/JefferyHcool/BiliNote &#x1f680; 快速开始 1. 克隆仓库 git clone https://github.com/JefferyHcool/BiliNote.git cd BiliNote mv .env.example .env2. 启动后端&#xff08;FastAPI&#xff09; cd backend pip insta…

bismark OT CTOT OB CTOB 以及mapping后的bam文件中的XG,XR列的含义

首先&#xff0c;OT&#xff0c;OB&#xff0c;CTOT&#xff0c;CTOB都是描述测序reads的&#xff0c;而不是描述参考基因组的。 bisul-fate建库会将DNA双链文库中非甲基化的C转化成U。转化结束后&#xff0c;被转化的U和互补链的G并不配对。此时正链&#xff08;&#xff0c;…

Android Native 之 adbd进程分析

目录 1、adbd守护进程 2、adbd权限降级 3、adbd命令解析 1&#xff09;adb shell 2&#xff09;adb root 3&#xff09;adb reboot 4、案例 1&#xff09;案例之实现不需要执行adb root命令自动具有root权限 2&#xff09;案例之实现不需要RSA认证直接能够使用adb she…

CAN通讯协议中各种参数解析

1.各种参数缩写 2.多帧传输时间参数解析 - Sender&#xff08;左侧&#xff09; 指的是 多帧数据的发送者&#xff0c;也就是&#xff1a; ECU&#xff08;被测系统 / 响应方&#xff09; - Receiver&#xff08;右侧&#xff09; 指的是 多帧数据的接收者&#xff0c;也就是…

网络攻防技术三:网络脆弱性分析

文章目录 一、影响安全的因素二、计算机网络三、网络体系结构脆弱性1、因特网容易被攻击的特性 四、典型网络协议安全性分析&#xff08;重要&#xff09;1、IPv42、RIP&#xff08;UDP)3、ICMP(UDP)4、ARP5、OSPF(IP数据报&#xff09;6、BGP(TCP)7、UDP8、TCP9、DNS(UDP)10、…

(八)登录认证与学生写作画像

本次将赵昱琨同学之前完成的学生写作画像与智能学习路径规划的后端与目前已有的后端框架进行整合。同时为了实现学生写作画像与智能学习路径规划&#xff0c;需要在之前简易的登录系统上进行重构&#xff0c;所以本次大规模重写了登录模块&#xff0c;同时发现很多过去冗余的代…

Netty学习example示例

文章目录 simpleServer端NettyServerNettyServerHandler Client端NettyClientNettyClientHandler tcp&#xff08;粘包和拆包&#xff09;Server端NettyTcpServerNettyTcpServerHandler Client端NettyTcpClientNettyTcpClientHandler protocolcodecCustomMessageDecoderCustomM…