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实战:写少一半代码,还更优雅!