跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

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

基于 Java 注解与反射机制,实现了两个实用工具:自定义日志注解 @Log 用于自动记录方法调用轨迹与耗时,以及参数校验注解 @NotNull 与 @Range 用于强制验证输入合法性。核心逻辑通过反射获取方法元数据并拦截执行,结合运行时保留策略实现声明式编程。相比传统硬编码,该方案显著减少冗余代码,提升可维护性,是理解 Spring 等框架底层原理的关键实践。

w795471发布于 2026/2/24更新于 2026/10/1981 浏览
Java 注解与反射实战:自定义日志与参数校验注解

引言

日常开发中,日志记录和参数校验往往充斥着重复代码。每个方法都要写'开始执行'、'参数是 xxx',或者判断 null、范围是否合法。这些工作机械且冗余。注解 + 反射正是解决这类问题的利器——用注解标记需要处理的地方,用反射自动执行逻辑,实现'一次定义,多处复用'。

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

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

案例一:自定义日志注解 @Log

需求分析

我们需要一个注解,标记在方法上后,能自动完成:记录方法开始执行的时间、打印方法参数(可选)、记录方法执行耗时、打印返回结果(可选)、捕获并记录方法抛出的异常。

定义注解

首先,定义 @Log 注解。关键在于元注解的配置:@Target(ElementType.METHOD) 限制注解仅用于方法,@Retention(RetentionPolicy.RUNTIME) 必须保留到运行时,否则反射无法获取。

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;
}

业务类使用

接下来创建一个业务类来使用这个注解。注意看 createOrder 方法,我们标记了描述、参数记录和返回值记录。

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) {}
    }
}

核心处理器实现

这是最关键的一步。通过反射拦截方法调用,解析 @Log 注解并执行日志记录逻辑。这里要注意异常的处理,我们捕获异常但继续抛出,不掩盖业务逻辑。

import java.lang.reflect.Method;
import java.util.Arrays;

public class LogAnnotationProcessor {
    public static Object executeWithLog(Object target, String methodName, Object... args) {
        try {
            Class<?>[] paramTypes = Arrays.stream(args)
                    .map(arg -> arg == null ? Object.class : arg.getClass())
                    .toArray(Class[]::new);
            Method method = target.getClass().getMethod(methodName, paramTypes);

            if (!method.isAnnotationPresent(Log.class)) {
                return method.invoke(target, args);
            }

            Log logAnnotation = method.getAnnotation(Log.class);
            long startTime = System.currentTimeMillis();
            System.out.println("\n===== 【日志开始】" + (logAnnotation.description().isEmpty() ? methodName : logAnnotation.description()) + " =====");

            if (logAnnotation.recordParams()) {
                System.out.println("参数:" + Arrays.toString(args));
            }

            Object result;
            try {
                result = method.invoke(target, args);
            } catch (Exception e) {
                if (logAnnotation.recordException()) {
                    System.out.println("执行异常:" + e.getCause().getMessage());
                }
                throw e;
            }

            long endTime = System.currentTimeMillis();
            System.out.println("耗时:" + (endTime - startTime) + "ms");
            if (logAnnotation.recordResult()) {
                System.out.println("返回值:" + result);
            }
            System.out.println("===== 【日志结束】" + (logAnnotation.description().isEmpty() ? methodName : logAnnotation.description()) + " =====");
            return result;
        } catch (Exception e) {
            throw new RuntimeException("方法执行失败:" + e.getMessage(), e);
        }
    }
}

验证效果

运行测试代码,观察控制台输出。可以看到参数、耗时、返回值都被自动记录了,异常情况也被捕获。

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) {}
    }
}

[图示:日志注解执行流程图]

案例二:自定义参数校验注解

需求分析

我们需要两个注解:@NotNull 标记参数不能为 null;@Range 标记数值参数必须在指定范围内。实现效果是方法调用时自动校验参数,不符合规则则抛出明确的异常信息。

定义校验注解

这两个注解都标记在参数上,所以 @Target 设为 ElementType.PARAMETER。

import java.lang.annotation.*;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
    String message() default "参数不能为 null";
}

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Range {
    long min() default 0;
    long max() default Long.MAX_VALUE;
    String message() default "参数不在指定范围内";
}

业务类使用

在 UserService 中应用这些注解,比如新增用户时校验 ID 和年龄。

public class UserService {
    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);
    }

    public void updatePoints(
            @NotNull String userId,
            @Range(min = 0, message = "积分不能为负数") int points) {
        System.out.println("更新积分成功:userId=" + userId + ", points=" + points);
    }
}

校验器实现

通过反射获取方法参数上的注解,逐个校验参数是否符合注解规则。注意对数值类型的转换处理。

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;

public class ParamValidator {
    public static void validate(Object target, String methodName, Object... args) throws Exception {
        Class<?>[] paramTypes = Arrays.stream(args)
                .map(arg -> arg == null ? Object.class : arg.getClass())
                .toArray(Class[]::new);
        Method method = target.getClass().getMethod(methodName, paramTypes);
        Parameter[] parameters = method.getParameters();

        for (int i = 0; i < parameters.length; i++) {
            Parameter param = parameters[i];
            Object arg = args[i];

            if (param.isAnnotationPresent(NotNull.class)) {
                NotNull notNull = param.getAnnotation(NotNull.class);
                if (arg == null) {
                    throw new IllegalArgumentException(notNull.message());
                }
            }

            if (param.isAnnotationPresent(Range.class) && arg instanceof Number) {
                Range range = param.getAnnotation(Range.class);
                long value = ((Number) arg).longValue();
                if (value < range.min() || value > range.max()) {
                    throw new IllegalArgumentException(range.message());
                }
            }
        }
        method.invoke(target, args);
    }
}

验证效果

测试正常参数、null 参数、超出范围参数等情况。

public class ValidatorTest {
    public static void main(String[] args) {
        UserService userService = new UserService();
        try { ParamValidator.validate(userService, "addUser", "user_001", 25); } catch (Exception e) {}
        try { ParamValidator.validate(userService, "addUser", null, 25); } catch (Exception e) { System.out.println("错误:" + e.getMessage()); }
        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()); }
    }
}

[图示:参数校验流程图]

设计原则与优化

注解设计三要素

明确作用范围,用 @Target 严格限制场景;合理生命周期,需要反射解析的注解必须用 @Retention(RUNTIME);属性精简实用,只保留必要属性并设置默认值。

反射解析优化技巧

实际项目中,Method、Parameter 等对象的获取有性能开销,可通过 ConcurrentHashMap 缓存注解与方法的映射关系。批量处理注解,用 getAnnotations() 一次性获取。异常友好提示,校验或日志失败时,异常信息要明确。

与现有框架对比

日志注解方面,Spring 的 @Log、Lombok 的 @Log4j2 功能更完善,但自定义注解可灵活适配业务需求。参数校验方面,JSR-303 规范提供了丰富的校验注解,但自定义注解可实现框架不支持的特殊校验。

总结

注解是'声明式编程'的载体,让代码更简洁、意图更明确;反射是注解的'执行引擎',让标记转化为实际逻辑。这对组合在框架开发中无处不在,掌握它们能让你从'使用者'升级为'设计者'。

扩展思考

尝试扩展本文案例:给日志注解增加 level 属性(INFO/ERROR),实现不同级别日志的输出;新增 @Pattern 注解,校验字符串是否符合正则表达式。

目录

  1. 引言
  2. 案例一:自定义日志注解 @Log
  3. 需求分析
  4. 定义注解
  5. 业务类使用
  6. 核心处理器实现
  7. 验证效果
  8. 案例二:自定义参数校验注解
  9. 需求分析
  10. 定义校验注解
  11. 业务类使用
  12. 校验器实现
  13. 验证效果
  14. 设计原则与优化
  15. 注解设计三要素
  16. 反射解析优化技巧
  17. 与现有框架对比
  18. 总结
  19. 扩展思考

更多推荐文章

查看全部
  • Vue 开源 AI 低代码表单设计器 FcDesigner 使用指南
  • 使用 SWIFT 框架微调 InternVL2-40B 模型
  • 大模型幻觉深度治理:技术体系、工程实践与未来演进
  • 华南理工大学开源中文主动健康大模型扁鹊(BianQue)
  • Llama-3.2-3B 本地部署实战:Ollama 与 Open WebUI 集成指南
  • OpenAI 官方 Prompt 工程指南详解:六大核心原则与实战技巧
  • 基于龙卷风优化算法的多无人机协同路径规划与 MATLAB 实现
  • Stable Diffusion XL 1.0 部署实战:基于 Streamlit 与阿里云 PAI-EAS 的模型封装
  • 面试题解析:消失的两个数字(位运算解法)
  • GitHub Copilot 登录失败排查指南
  • 发那科机器人核心指令详解
  • GitHub 热门 Claude Skills 精选与实战指南
  • 基于射频和深度学习的无人机检测识别与开源数据库构建
  • Javashop 商城系统技术架构与功能特性解析
  • Python 与前端集成:构建全栈应用
  • 基于 Milvus 与混合检索的云厂商文档智能问答系统:Java SpringBoot 实现
  • SkyWalking 与 Spring Cloud Alibaba 全链路追踪实战
  • 大模型工程师学习路线:从基础到上手
  • C++ 模板进阶:非类型参数与特化机制
  • 数论之中国剩余定理:从孙子算经到算法竞赛实现

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online