Spring Boot 自定义注解实战:用常见的5个高频案例带你飞!

Spring Boot 自定义注解实战:用常见的5个高频案例带你飞!
在这里插入图片描述
🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
🍎 《前端技术》专栏以实战为主介绍日常开发中前端应用的一些功能以及技巧,均附有完整的代码示例
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
👍《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

Spring Boot 自定义注解实战:用常见的几个高频案例带你飞!

在Java开发中一说到注解,相信小伙伴们经常会用到 Spring Boot 提供的各种注解,比如 @RestController@Autowired@Transactional 等。这些注解极大地提高了开发效率,但有时候业务中会出现一些重复逻辑,这时就需要我们自定义注解来提升代码的优雅性和可维护性。

自定义注解是一种强大的元编程工具,允许在不修改原有代码逻辑的情况下,为程序添加额外的功能。通过AOP面向切面编程)与自定义注解的结合,我们可以实现关注点分离,让业务代码更加清晰简洁。

在这里插入图片描述

自定义注解有哪些好处?

代码复用:将通用逻辑封装到注解中
业务解耦:横切关注点与核心业务逻辑分离
声明式编程:通过注解配置行为,代码更直观
可维护性:通用逻辑集中管理,修改更方便


自定义注解的原理

Spring Boot 自定义注解的底层原理主要依赖于:

  • Java 注解机制(@interface 定义注解)
  • AOP(面向切面编程) 或 拦截器 结合反射来解析注解
  • Spring 容器在运行时自动识别和织入逻辑

自定义注解的实现步骤

引入依赖

首先确保pom.xml中包含必要的依赖:

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

定义自定义注解

以一个最基础的自定义注解为例:

importjava.lang.annotation.*;@Target(ElementType.METHOD)// 注解作用目标:方法@Retention(RetentionPolicy.RUNTIME)// 运行时生效@Documentedpublic@interfaceMyAnnotation{Stringvalue()default"default";}
注解说明:
@Target:指定注解作用的范围(类、方法、字段、参数…)
@Retention:指定注解生命周期(源码、编译期、运行时)
@Documented:生成 Javadoc 时包含注解信息

常见的自定义注解案例

下面博主讲完整演示几个日常开发中我们常见的自定义注解案例来让大家深入的了解

❶ 自定义日志注解

定义注解
效果:调用接口时,自动打印方法耗时和相关日志

/** * 方法日志注解 * 用于自动记录方法入参、出参和执行时间 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceMethodLog{Stringvalue()default"";booleanprintArgs()defaulttrue;booleanprintResult()defaulttrue;booleantiming()defaulttrue;}

切面实现
这里仅仅以打印输出为案例,实际生产环境中,小伙伴们可以结合数据库、日志系统等将信息记录入库

@Aspect@Component@Slf4jpublicclassMethodLogAspect{@Around("@annotation(methodLog)")publicObjectaround(ProceedingJoinPoint joinPoint,MethodLog methodLog)throwsThrowable{String methodName =getMethodName(joinPoint);String className = joinPoint.getTarget().getClass().getSimpleName();// 记录开始时间long startTime =System.currentTimeMillis();if(methodLog.printArgs()){Object[] args = joinPoint.getArgs(); log.info("[{}#{}] 方法调用, 参数: {}", className, methodName,Arrays.toString(args));}else{ log.info("[{}#{}] 方法调用", className, methodName);}try{Object result = joinPoint.proceed();if(methodLog.printResult()){ log.info("[{}#{}] 方法返回: {}", className, methodName, result);}if(methodLog.timing()){long cost =System.currentTimeMillis()- startTime; log.info("[{}#{}] 方法执行耗时: {}ms", className, methodName, cost);}return result;}catch(Exception e){ log.error("[{}#{}] 方法执行异常: {}", className, methodName, e.getMessage());throw e;}}privateStringgetMethodName(ProceedingJoinPoint joinPoint){return joinPoint.getSignature().getName();}}

使用示例
以用户接口为例,创建用户的时候会记录该接口会打印相关的信息日志

@RestController@RequestMapping("/api/user")publicclassUserController{@PostMapping@MethodLog(value ="创建用户", printArgs =true, printResult =true, timing =true)publicUsercreateUser(@RequestBodyUser user){// 业务逻辑return userService.save(user);}@GetMapping("/{id}")@MethodLog("根据ID查询用户")publicUsergetUser(@PathVariableLong id){return userService.findById(id);}}

❷ 自定义参数校验注解

通常我们在Controller中进行数据校验都是用validation, 可以大大节省我们参数校验的时间,虽然validation 默认的注解已经足以应付我们工作中大部分场景,但还是会有一些参数校验有其它的一些验证要求,那么就可以用到自定义参数校验注解。

你也查阅博主之前写的 【Spring Boot数据校验validation实战:写少一半代码,还更优雅!】学习Spring Boot数据校验

定义注解
效果:提交手机号不合法时,自动抛出校验异常

importjavax.validation.Constraint;importjavax.validation.Payload;importjava.lang.annotation.*;@Target({ElementType.FIELD,ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy =PhoneValidator.class)// 绑定校验器public@interfacePhone{Stringmessage()default"手机号格式错误";Class<?>[]groups()default{};Class<?extendsPayload>[]payload()default{};}

实现校验器

这里就简单验证一下是否正确的手机号,小伙伴们可以加入自己需要的验证逻辑,比如仅限移动用户等

importjavax.validation.ConstraintValidator;importjavax.validation.ConstraintValidatorContext;publicclassPhoneValidatorimplementsConstraintValidator<Phone,String>{@OverridepublicbooleanisValid(String value,ConstraintValidatorContext context){return value !=null&& value.matches("^1[3-9]\\d{9}$");}}

使用示例

importjavax.validation.Valid;importjavax.validation.constraints.NotBlank;@RestControllerpublicclassRegisterController{@PostMapping("/register")publicStringregister(@Valid@RequestBodyUserDTO userDTO){return"注册成功";}publicstaticclassUserDTO{@NotBlankprivateString name;@PhoneprivateString phone;// getter/setter}}

❸ 自定义权限校验注解

本次我们模拟Spring Security中的@PreAuthorize注解,想完整学习@PreAuthorize注解用法的小伙伴可以参考博主Spring Security专栏下的 【最新Spring Security实战教程(七)方法级安全控制@PreAuthorize注解的灵活运用】
这里我们就模拟一下全县校验的功能

定义注解

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceCheckPermission{Stringvalue();// 权限标识}

实现 AOP 权限校验

importorg.aspectj.lang.JoinPoint;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.annotation.Before;importorg.springframework.stereotype.Component;@Aspect@ComponentpublicclassPermissionAspect{@Before("@annotation(checkPermission)")publicvoidcheck(JoinPoint joinPoint,CheckPermission checkPermission){String requiredPermission = checkPermission.value();// 模拟从上下文获取当前用户权限String userPermission ="USER";if(!userPermission.equals(requiredPermission)){thrownewRuntimeException("权限不足,缺少:"+ requiredPermission);}}}

使用示例

@RestControllerpublicclassAdminController{@CheckPermission("ADMIN")@GetMapping("/admin")publicStringadminPage(){return"管理员页面";}}

❹ 自定义分布式限流注解

定义注解

/** * 限流注解 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceRateLimit{Stringkey()default"";intlimit()default100;inttimeWindow()default60;// 时间窗口,单位:秒Stringmessage()default"访问过于频繁,请稍后再试";}

切面实现

@Aspect@Component@Slf4jpublicclassRateLimitAspect{privatefinalMap<String,RateLimiter> limiterMap =newConcurrentHashMap<>();@Before("@annotation(rateLimit)")publicvoidrateLimitCheck(RateLimit rateLimit){String key =generateKey(rateLimit);RateLimiter limiter = limiterMap.computeIfAbsent(key, k ->RateLimiter.create(rateLimit.limit()/(double) rateLimit.timeWindow()));if(!limiter.tryAcquire()){thrownewRuntimeException(rateLimit.message());}}privateStringgenerateKey(RateLimit rateLimit){String key = rateLimit.key();if(StringUtils.isEmpty(key)){// 可以结合用户信息、IP等生成唯一keyreturn"rate_limit:"+System.identityHashCode(rateLimit);}return"rate_limit:"+ key;}}// 简单的令牌桶限流器实现classRateLimiter{privatefinaldouble capacity;privatefinaldouble refillTokensPerOneMillis;privatedouble availableTokens;privatelong lastRefillTimestamp;publicstaticRateLimitercreate(double permitsPerSecond){returnnewRateLimiter(permitsPerSecond);}privateRateLimiter(double permitsPerSecond){this.capacity = permitsPerSecond;this.refillTokensPerOneMillis = permitsPerSecond /1000.0;this.availableTokens = permitsPerSecond;this.lastRefillTimestamp =System.currentTimeMillis();}publicsynchronizedbooleantryAcquire(){refill();if(availableTokens <1){returnfalse;} availableTokens -=1;returntrue;}privatevoidrefill(){long currentTime =System.currentTimeMillis();if(currentTime > lastRefillTimestamp){long millisSinceLastRefill = currentTime - lastRefillTimestamp;double refill = millisSinceLastRefill * refillTokensPerOneMillis;this.availableTokens =Math.min(capacity, availableTokens + refill);this.lastRefillTimestamp = currentTime;}}}

使用示例

@RestController@RequestMapping("/api")publicclassApiController{@GetMapping("/public/data")@RateLimit(limit =10, timeWindow =60, message ="接口调用频率超限")publicApiResponsegetPublicData(){returnApiResponse.success("公开数据");}@PostMapping("/submit")@RateLimit(key ="submit_limit", limit =5, timeWindow =30)publicApiResponsesubmitData(@RequestBodyData data){// 处理提交returnApiResponse.success("提交成功");}}

❺ 自定义加解密注解

可参考博主之前写的 【Spring Boot中整合Jasypt 使用自定义注解+AOP实现敏感字段的加解密】进行学习,这里就不再赘述了!


总结

以上通过4个案例演示,完整讲解了Spring Boot 自定义注解的使用,通过合理使用自定义注解,我们可以大幅提升代码的可读性、可维护性和复用性。在实际项目中,可以根据业务需求灵活组合和扩展这些注解,构建更加健壮和安全的应用程序。

如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家一键三连给博主一点点鼓励!


专栏回顾:
01 Spring Boot 整合 spring-boot-starter-mail 实现邮件发送和账户激活
02 使用Spring Boot自定义注解 + AOP实现基于IP的接口限流和黑白名单
03 Spring Boot 使用自定义注解和自定义线程池实现异步日志记录
04 Spring Boot整合Jasypt 库实现配置文件和数据库字段敏感数据的加解密
05 Spring Boot中整合Jasypt 使用自定义注解+AOP实现敏感字段的加解密
06 Spring Boot整合WebSocket和Redis实现直播间在线人数统计功能
07 Spring Boot通过自定义注解和Redis+Lua脚本实现接口限流
08 Spring Boot整合Redis通过Zset数据类型+定时任务实现延迟队列
09 Spring Boot整合Redis实现发布/订阅功能
10 Spring Boot集成 Spring Retry 实现容错重试机制并附源码
11 Spring Boot 3 整合 SpringDoc OpenAPI 生成接口文档
12 Spring Boot 整合开源 Tess4J库 实现OCR图片文字识别
13 Spring Boot 实现 AOP 动态热插拔功能并附DEMO源码
14 Spring Boot中@Async注解的使用及原理 + 常见问题及解决方案
15 Spring Boot集成OpenPDF和Freemarker实现PDF导出功能并附水印
16 使用Spring Boot整合ip2region获取客户端IP地理位置信息
17 SpringBoot中MyBatis使用自定义TypeHandler
18 Spring Boot 集成 PDFBox 实现PDF电子签章的简单应用
19 实现重试只知道Spring Retry?试试Spring Boot 整合 Fast Retry 来实现重试机制
20 在Spring Boot中使用SeeEmitter类实现EventStream流式编程将实时事件推送至客户端
21 Spring Boot 整合 ShedLock 处理定时任务重复执行的问题
22 视频续播功能实现 - 断点续看从前端到 Spring Boot 后端
23 前端与 Spring Boot 后端无感 Token 刷新 - 从原理到全栈实践
24 一文让你测底明白如何在 Spring Boot 上传中将 MultipartFile 转 File 对象
25 Spring Boot数据校验validation实战:写少一半代码,还更优雅!

Read more

Claude Code实战指南:Java大型项目AI编程革命,终端Agent国内落地全解析

Claude Code实战指南:Java大型项目AI编程革命,终端Agent国内落地全解析

从对话框到终端 Agent:Claude Code 在 Java 大型项目中的深度实战与国内落地全指南 在软件工程的漫长演进史中,我们正经历着一场从“辅助编程”向“自主代理”跨越的范式转移。回望过去几年,GitHub Copilot 让我们习惯了代码补全,Cursor 让我们体验了 IDE 级的对话重构,但这些工具本质上仍未脱离“对话框”的束缚。Claude Code 的出现标志着 AI 编程正式回归了开发者最神圣的领地——终端(CLI)。 作为一个拥有十五年经验的架构师,我深知终端不仅是输入命令的地方,更是连接文件系统、编译器、测试框架和部署流水线的神经中枢。当 AI 能够以 Agent 的身份直接在终端运行,它就不再只是一个“建议者”,而是一个能够真正“交付结果”的数字队友。 核心解析:为何终端 Agent 是

By Ne0inhk
Java 部署:K8s Service 与 Ingress 配置(外部访问)

Java 部署:K8s Service 与 Ingress 配置(外部访问)

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕Java部署这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * Java 部署:K8s Service 与 Ingress 配置(外部访问) 🚀 * 为什么需要 Service 和 Ingress?🤔 * 准备工作:一个简单的 Java Web 应用 🧪 * 项目结构 * Maven 依赖(pom.xml) * 主启动类(DemoApplication.java) * 控制器(ApiController.java) * 配置文件(application.properties) * 构建与测试本地运行 * 容器化 Java 应用 🐳 * 编写 Dockerfile * 构建镜像

By Ne0inhk

芯片制造企业Java如何通过NIO技术加速视频分块上传的磁盘I/O效率?

大文件传输组件选型与实现方案 作为江苏某软件公司前端工程师,针对公司当前20G级大文件传输需求,我进行了深入的技术调研和方案评估。以下是基于公司现有技术栈(Vue2 + JSP + 国产化环境)的完整解决方案。 一、需求分析 1. 核心功能: * 支持20GB+大文件上传/下载 * 完整文件夹上传(保留层级结构) * 进度持久化(防刷新/关闭丢失) * 断点续传 * 秒传功能(MD5校验) 2. 兼容性要求: * 浏览器:Chrome/Firefox/Edge + 信创浏览器(龙芯/红莲花/奇安信) * 操作系统:Windows/统信UOS/中标麒麟/银河麒麟 * 数据库:SQL Server/MySQL/Oracle + 达梦/人大金仓 3. 技术约束: * 前端:Vue2-cli框架

By Ne0inhk
Java的基础知识

Java的基础知识

目录 == 和 equals() 的区别 hashCode() 有什么用? 重写equals为什么要重写hashcode? 为什么用BigDecimal不用float/double计算出现什么问题? 自动装箱与拆箱 深拷贝和浅拷贝区别?什么是引用拷贝 面向对象的三大特征 面向对象和面向过程的区别 String的不可变性 String s1 = new String("abc");创建了几个对象? String和StringBuffer和StringBuilder区别 字符串拼接用“+” 还是 StringBuilder? 字符串常量池的作用了解吗? == 和 equals() 的区别 == 对于基本类型和引用类型的作用效果是不同的: 对于基本数据类型来说,== 比较的是值 对于引用数据类型来说,== 比较的是对象的内存地址 equals() 方法存在两种使用情况: 类没有重写 equals() 方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object 类equals()方法。 类重写了

By Ne0inhk