Java 注解与反射实战:手把手实现自定义日志与参数校验注解

Java 注解与反射实战:手把手实现自定义日志与参数校验注解

前言:为什么需要自定义注解?

在日常开发中,我们经常遇到两类重复工作:

日志记录:每个重要方法都要写 "开始执行"、"参数是 xxx"、"执行结束" 的代码;参数校验:判断输入是否为 null、年龄是否在合理范围、手机号格式是否正确等。

这些工作机械且冗余,而注解 + 反射正是解决这类问题的 "银弹"—— 用注解标记需要处理的地方,用反射自动执行逻辑,实现 "一次定义,多处复用"。

本文将带你从零实现两个实用案例:

  1. 自定义日志注解@Log:自动记录方法调用细节;
  2. 自定义参数校验注解@NotNull@Range:自动校验方法参数合法性。

全程实战,代码可直接运行,搭配图解帮你吃透底层逻辑。

案例一:自定义日志注解@Log—— 自动记录方法调用轨迹

需求分析

我们需要一个注解,标记在方法上后,能自动完成:

记录方法开始执行的时间;打印方法参数(可选);记录方法执行耗时;打印返回结果(可选);捕获并记录方法抛出的异常。

步骤 1:定义@Log注解

import java.lang.annotation.*; // 只能标记在方法上 @Target(ElementType.METHOD) // 运行时保留,允许反射获取 @Retention(RetentionPolicy.RUNTIME) public @interface Log { // 操作描述(如"创建订单") String description() default ""; // 是否记录参数 boolean recordParams() default true; // 是否记录返回值 boolean recordResult() default true; // 是否记录异常 boolean recordException() default true; } 

元注解说明

@Target(ElementType.METHOD):限制注解仅用于方法(符合日志记录的场景);@Retention(RetentionPolicy.RUNTIME):必须保留到运行时,否则反射无法获取。

步骤 2:创建使用@Log注解的业务类

public class OrderService { // 标记日志:记录参数和返回值,描述为"创建订单" @Log(description = "创建订单", recordParams = true, recordResult = true) public String createOrder(String userId, double amount) { if (amount <= 0) { throw new IllegalArgumentException("金额必须大于0"); } // 模拟业务耗时 try { Thread.sleep(100); } catch (InterruptedException e) {} return "ORDER_" + System.currentTimeMillis(); } // 标记日志:不记录返回值(无返回值),描述为"取消订单" @Log(description = "取消订单", recordParams = true, recordResult = false) public void cancelOrder(String orderId) { if (orderId == null || orderId.isEmpty()) { throw new IllegalArgumentException("订单ID不能为空"); } // 模拟业务耗时 try { Thread.sleep(50); } catch (InterruptedException e) {} } } 

步骤 3:实现注解解析器(核心逻辑)

通过反射拦截方法调用,解析@Log注解并执行日志记录逻辑:

import java.lang.reflect.Method; import java.util.Arrays; public class LogAnnotationProcessor { /** * 执行带日志注解的方法 * @param target 目标对象 * @param methodName 方法名 * @param args 方法参数 * @return 方法返回值 */ public static Object executeWithLog(Object target, String methodName, Object... args) { try { // 1. 获取方法对应的Class对象和参数类型 Class<?>[] paramTypes = Arrays.stream(args) .map(arg -> arg == null ? Object.class : arg.getClass()) .toArray(Class[]::new); Method method = target.getClass().getMethod(methodName, paramTypes); // 2. 检查方法是否有@Log注解,无则直接执行 if (!method.isAnnotationPresent(Log.class)) { return method.invoke(target, args); } // 3. 解析注解属性 Log logAnnotation = method.getAnnotation(Log.class); String description = logAnnotation.description(); boolean recordParams = logAnnotation.recordParams(); boolean recordResult = logAnnotation.recordResult(); boolean recordException = logAnnotation.recordException(); // 4. 记录方法开始日志 long startTime = System.currentTimeMillis(); System.out.println("\n===== 【日志开始】" + (description.isEmpty() ? methodName : description) + " ====="); if (recordParams) { System.out.println("参数:" + Arrays.toString(args)); } // 5. 执行目标方法(捕获异常并记录) Object result; try { result = method.invoke(target, args); } catch (Exception e) { // 记录异常信息 if (recordException) { System.out.println("执行异常:" + e.getCause().getMessage()); } throw e; // 继续抛出异常,不掩盖业务逻辑 } // 6. 记录方法结束日志 long endTime = System.currentTimeMillis(); System.out.println("耗时:" + (endTime - startTime) + "ms"); if (recordResult) { System.out.println("返回值:" + result); } System.out.println("===== 【日志结束】" + (description.isEmpty() ? methodName : description) + " ====="); return result; } catch (Exception e) { // 处理反射或业务异常(实际项目中可转为自定义异常) throw new RuntimeException("方法执行失败:" + e.getMessage(), e); } } } 

步骤 4:测试日志注解效果

public class LogTest { public static void main(String[] args) { OrderService orderService = new OrderService(); // 测试正常创建订单 LogAnnotationProcessor.executeWithLog(orderService, "createOrder", "user_001", 99.9); // 测试取消订单 LogAnnotationProcessor.executeWithLog(orderService, "cancelOrder", "ORDER_123456"); // 测试异常场景(金额为负数) try { LogAnnotationProcessor.executeWithLog(orderService, "createOrder", "user_002", -10.0); } catch (Exception e) { // 此处仅捕获,不处理(异常已被日志记录) } } } 

执行结果

===== 【日志开始】创建订单 ===== 参数:[user_001, 99.9] 耗时:101ms 返回值:ORDER_1698765432100 ===== 【日志结束】创建订单 ===== ===== 【日志开始】取消订单 ===== 参数:[ORDER_123456] 耗时:50ms ===== 【日志结束】取消订单 ===== ===== 【日志开始】创建订单 ===== 参数:[user_002, -10.0] 执行异常:金额必须大于0 ===== 【日志结束】创建订单 ===== 

日志注解执行流程图解

(注:核心展示流程节点:调用方法→反射检查注解→记录开始日志→执行方法→记录结束日志)

案例二:自定义参数校验注解 —— 告别重复的 if-else

需求分析

我们需要两个注解:

@NotNull:标记参数不能为 null;@Range:标记数值参数必须在指定范围内(如年龄 1-120 岁)。

实现效果:方法调用时自动校验参数,不符合规则则抛出明确的异常信息。

步骤 1:定义校验注解

1.1 @NotNull注解
import java.lang.annotation.*; @Target(ElementType.PARAMETER) // 仅用于方法参数 @Retention(RetentionPolicy.RUNTIME) public @interface NotNull { // 校验失败的提示信息 String message() default "参数不能为null"; } 
1.2 @Range注解
import java.lang.annotation.*; @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface Range { long min() default 0; // 最小值(包含) long max() default Long.MAX_VALUE; // 最大值(包含) String message() default "参数不在指定范围内"; } 

步骤 2:创建使用校验注解的业务类

public class UserService { // 新增用户:ID不能为空,年龄必须1-120岁 public void addUser( @NotNull(message = "用户ID不能为空") String userId, @Range(min = 1, max = 120, message = "年龄必须在1-120岁之间") int age) { System.out.println("新增用户成功:userId=" + userId + ", age=" + age); } // 更新积分:积分必须≥0 public void updatePoints( @NotNull String userId, @Range(min = 0, message = "积分不能为负数") int points) { System.out.println("更新积分成功:userId=" + userId + ", points=" + points); } } 

步骤 3:实现参数校验器(核心逻辑)

通过反射获取方法参数上的注解,逐个校验参数是否符合注解规则:

import java.lang.reflect.Method; import java.lang.reflect.Parameter; public class ParamValidator { /** * 校验方法参数是否符合注解规则 * @param target 目标对象 * @param methodName 方法名 * @param args 方法参数 * @throws IllegalArgumentException 校验失败时抛出 */ public static void validate(Object target, String methodName, Object... args) throws Exception { // 1. 获取方法对象 Class<?>[] paramTypes = Arrays.stream(args) .map(arg -> arg == null ? Object.class : arg.getClass()) .toArray(Class[]::new); Method method = target.getClass().getMethod(methodName, paramTypes); // 2. 获取方法参数列表(包含注解信息) Parameter[] parameters = method.getParameters(); // 3. 遍历参数,执行校验 for (int i = 0; i < parameters.length; i++) { Parameter param = parameters[i]; Object arg = args[i]; // 当前参数值 // 3.1 校验@NotNull注解 if (param.isAnnotationPresent(NotNull.class)) { NotNull notNull = param.getAnnotation(NotNull.class); if (arg == null) { throw new IllegalArgumentException(notNull.message()); } } // 3.2 校验@Range注解(仅对数值类型生效) if (param.isAnnotationPresent(Range.class) && arg instanceof Number) { Range range = param.getAnnotation(Range.class); long value = ((Number) arg).longValue(); // 转为long统一处理 if (value < range.min() || value > range.max()) { throw new IllegalArgumentException(range.message()); } } } // 4. 校验通过,执行方法 method.invoke(target, args); } } 

步骤 4:测试参数校验效果

public class ValidatorTest { public static void main(String[] args) { UserService userService = new UserService(); // 测试正常参数 try { ParamValidator.validate(userService, "addUser", "user_001", 25); } catch (Exception e) { System.out.println("错误:" + e.getMessage()); } // 测试null参数(userId为null) try { ParamValidator.validate(userService, "addUser", null, 25); } catch (Exception e) { System.out.println("错误:" + e.getMessage()); } // 测试年龄超出范围(150岁) try { ParamValidator.validate(userService, "addUser", "user_002", 150); } catch (Exception e) { System.out.println("错误:" + e.getMessage()); } // 测试积分负数 try { ParamValidator.validate(userService, "updatePoints", "user_003", -10); } catch (Exception e) { System.out.println("错误:" + e.getMessage()); } } } 

执行结果

新增用户成功:userId=user_001, age=25 错误:用户ID不能为空 错误:年龄必须在1-120岁之间 错误:积分不能为负数 

参数校验流程图解

实战总结:自定义注解的设计原则与优化方向

1. 注解设计三要素

明确作用范围:用@Target严格限制注解的使用场景(如日志注解只用于方法);合理生命周期:需要反射解析的注解必须用@Retention(RUNTIME)属性精简实用:只保留必要的属性(如日志注解的description、校验注解的message),并设置合理默认值。

2. 反射解析优化技巧

缓存反射结果MethodParameter等对象的获取有性能开销,可通过ConcurrentHashMap缓存注解与方法的映射关系;批量处理注解:用getAnnotations()一次性获取所有注解,避免多次反射调用;异常友好提示:校验或日志失败时,异常信息要明确(如 "年龄必须在 1-120 岁之间" 而非 "参数错误")。

3. 与现有框架的对比

日志注解:Spring 的@Log、Lombok 的@Log4j2功能更完善,但自定义注解可灵活适配业务需求(如对接特定日志系统);参数校验:JSR-303 规范(javax.validation)提供了丰富的校验注解,但自定义注解可实现框架不支持的特殊校验(如手机号格式、身份证号规则)。

结语:注解 + 反射 = 代码的 "隐形翅膀"

通过本文的两个实战案例,你应该体会到:

注解是 "声明式编程" 的载体,让代码更简洁、意图更明确;反射是注解的 "执行引擎",让标记转化为实际逻辑。

这对组合在框架开发(如 Spring、MyBatis)中无处不在,掌握它们能让你从 "使用者" 升级为 "设计者"。

动手练习:尝试扩展本文案例 ——

  1. 给日志注解增加level属性(INFO/ERROR),实现不同级别日志的输出;
  2. 新增@Pattern注解,校验字符串是否符合正则表达式(如手机号、邮箱)。

欢迎在评论区分享你的实现思路!

Read more

Flutter 三方库 tonik 的鸿蒙化适配指南 - 实现严谨的音乐理论建模与五线谱逻辑运算、支持端侧音程解析与动态和弦推导实战

Flutter 三方库 tonik 的鸿蒙化适配指南 - 实现严谨的音乐理论建模与五线谱逻辑运算、支持端侧音程解析与动态和弦推导实战

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 tonik 的鸿蒙化适配指南 - 实现严谨的音乐理论建模与五线谱逻辑运算、支持端侧音程解析与动态和弦推导实战 前言 在进行 Flutter for OpenHarmony 的音乐教育、数字编曲(DAW)或智能辅助乐器类应用开发时,如何低成本且精确地描述“音符(Note)”、“音程(Interval)”或“和弦(Chord)”之间的数学逻辑?tonik 是一个功能完备的音乐理论(Music Theory)驱动库。它能将抽象的乐理知识转化为极其严谨的 Dart 对象模型。本文将介绍如何在鸿蒙端构建极致的智慧音律体验。 一、原直观解析 / 概念介绍 1.1 基础原理 tonik 基于标准的十二平均律(Equal Temperament)算法,

By Ne0inhk
Apache IoTDB(11):分段聚合深度解析——从原理到实战的完整指南

Apache IoTDB(11):分段聚合深度解析——从原理到实战的完整指南

引言 在工业物联网时代,时序数据的高效处理成为企业数字化转型的核心挑战。Apache IoTDB作为专为物联网设计的时序数据库,其分段分组聚合能力堪称数据处理的"瑞士军刀"。 Apache IoTDB 时序数据库【系列篇章】: No.文章地址(点击进入)1Apache IoTDB(1):时序数据库介绍与单机版安装部署指南2Apache IoTDB(2):时序数据库 IoTDB 集群安装部署的技术优势与适用场景分析3Apache IoTDB(3):时序数据库 IoTDB Docker部署从单机到集群的全场景部署与实践指南4Apache IoTDB(4):深度解析时序数据库 IoTDB 在Kubernetes 集群中的部署与实践指南5Apache IoTDB(5):深度解析时序数据库 IoTDB 中 AINode 工具的部署与实践6Apache IoTDB(6):深入解析数据库管理操作——增删改查与异构数据库实战指南7Apache IoTDB(7):设备模板管理—

By Ne0inhk
Flutter 三方库 dead_code_analyzer 的鸿蒙化适配指南 - 彻底清除无用代码、精简鸿蒙产物包体积、提升工程纯净度

Flutter 三方库 dead_code_analyzer 的鸿蒙化适配指南 - 彻底清除无用代码、精简鸿蒙产物包体积、提升工程纯净度

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 dead_code_analyzer 的鸿蒙化适配指南 - 彻底清除无用代码、精简鸿蒙产物包体积、提升工程纯净度 在鸿蒙跨平台开发的长期迭代中,废弃的业务逻辑、过时的工具函数就像“工程血栓”,不仅拖慢编译速度,还会无端增加最终 .app 包的体积。今天我们聊聊如何用 dead_code_analyzer 像外科医生一样精准切除这些冗余毒瘤。 前言 很多开发者认为“我不调用它,它就不占空间”。但在复杂的 Flutter 依赖树中,一些被标注为 public 但实际未引用的类,依然可能被编译器包含。特别是在 OpenHarmony 这种对包体积、启动性能有极致要求的系统下,工程的“纯净度”直接影响用户体验。 dead_code_analyzer 是一款专门针对

By Ne0inhk
Flutter for OpenHarmony:Flutter 三方库 os_detect — 精准洞察鸿蒙系统的底层脉络(适配鸿蒙 HarmonyOS Next ohos)

Flutter for OpenHarmony:Flutter 三方库 os_detect — 精准洞察鸿蒙系统的底层脉络(适配鸿蒙 HarmonyOS Next ohos)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net。 Flutter for OpenHarmony:Flutter 三方库 os_detect — 精准洞察鸿蒙系统的底层脉络(适配鸿蒙 HarmonyOS Next ohos) 在进行 Flutter for OpenHarmony 跨平台开发时,我们经常需要处理“差异化”的需求。有的功能可能只在真正的 OpenHarmony 原生环境下运行(如特定的 N-API 调用),而在 Web 或其他桌面模拟器环境下则需要进行降级处理。 传统的 Platform.isAndroid 或 kIsWeb 在处理日渐复杂的鸿蒙生态环境时,往往显得力不从心。os_detect 库提供了一套更轻量、更可靠的系统环境感知方案,能帮助我们精准识别应用正跑在哪个“灵魂”之下。 一、为什么需要系统环境检测?

By Ne0inhk