最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java注解與反射實戰(zhàn)之實現(xiàn)自定義日志與參數(shù)校驗注解

 更新時間:2026年01月05日 09:24:29   作者:梵得兒SHI  
在Java開發(fā)中,注解(Annotation)是一種強大的元數(shù)據(jù)形式,它能夠為程序元素(如類、方法、變量等)提供附加信息,這篇文章主要介紹了Java注解與反射實戰(zhàn)之實現(xiàn)自定義日志與參數(shù)校驗注解的相關(guān)資料,需要的朋友可以參考下

前言:為什么需要自定義注解?

在日常開發(fā)中,我們經(jīng)常遇到兩類重復工作:

  • 日志記錄:每個重要方法都要寫 "開始執(zhí)行"、"參數(shù)是 xxx"、"執(zhí)行結(jié)束" 的代碼;
  • 參數(shù)校驗:判斷輸入是否為 null、年齡是否在合理范圍、手機號格式是否正確等。

這些工作機械且冗余,而注解 + 反射正是解決這類問題的 "銀彈"—— 用注解標記需要處理的地方,用反射自動執(zhí)行邏輯,實現(xiàn) "一次定義,多處復用"。

本文將帶你從零實現(xiàn)兩個實用案例:

  1. 自定義日志注解@Log:自動記錄方法調(diào)用細節(jié);
  2. 自定義參數(shù)校驗注解@NotNull@Range:自動校驗方法參數(shù)合法性。

全程實戰(zhàn),代碼可直接運行,搭配圖解幫你吃透底層邏輯。

案例一:自定義日志注解@Log—— 自動記錄方法調(diào)用軌跡

需求分析

我們需要一個注解,標記在方法上后,能自動完成:

  • 記錄方法開始執(zhí)行的時間;
  • 打印方法參數(shù)(可選);
  • 記錄方法執(zhí)行耗時;
  • 打印返回結(jié)果(可選);
  • 捕獲并記錄方法拋出的異常。

步驟 1:定義@Log注解

import java.lang.annotation.*;

// 只能標記在方法上
@Target(ElementType.METHOD)
// 運行時保留,允許反射獲取
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    // 操作描述(如"創(chuàng)建訂單")
    String description() default "";
    
    // 是否記錄參數(shù)
    boolean recordParams() default true;
    
    // 是否記錄返回值
    boolean recordResult() default true;
    
    // 是否記錄異常
    boolean recordException() default true;
}

元注解說明

  • @Target(ElementType.METHOD):限制注解僅用于方法(符合日志記錄的場景);
  • @Retention(RetentionPolicy.RUNTIME):必須保留到運行時,否則反射無法獲取。

步驟 2:創(chuàng)建使用@Log注解的業(yè)務類

public class OrderService {
    // 標記日志:記錄參數(shù)和返回值,描述為"創(chuàng)建訂單"
    @Log(description = "創(chuàng)建訂單", recordParams = true, recordResult = true)
    public String createOrder(String userId, double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("金額必須大于0");
        }
        // 模擬業(yè)務耗時
        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不能為空");
        }
        // 模擬業(yè)務耗時
        try { Thread.sleep(50); } catch (InterruptedException e) {}
    }
}

步驟 3:實現(xiàn)注解解析器(核心邏輯)

通過反射攔截方法調(diào)用,解析@Log注解并執(zhí)行日志記錄邏輯:

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

public class LogAnnotationProcessor {

    /**
     * 執(zhí)行帶日志注解的方法
     * @param target 目標對象
     * @param methodName 方法名
     * @param args 方法參數(shù)
     * @return 方法返回值
     */
    public static Object executeWithLog(Object target, String methodName, Object... args) {
        try {
            // 1. 獲取方法對應的Class對象和參數(shù)類型
            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注解,無則直接執(zhí)行
            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("參數(shù):" + Arrays.toString(args));
            }

            // 5. 執(zhí)行目標方法(捕獲異常并記錄)
            Object result;
            try {
                result = method.invoke(target, args);
            } catch (Exception e) {
                // 記錄異常信息
                if (recordException) {
                    System.out.println("執(zhí)行異常:" + e.getCause().getMessage());
                }
                throw e; // 繼續(xù)拋出異常,不掩蓋業(yè)務邏輯
            }

            // 6. 記錄方法結(jié)束日志
            long endTime = System.currentTimeMillis();
            System.out.println("耗時:" + (endTime - startTime) + "ms");
            if (recordResult) {
                System.out.println("返回值:" + result);
            }
            System.out.println("===== 【日志結(jié)束】" + (description.isEmpty() ? methodName : description) + " =====");

            return result;
        } catch (Exception e) {
            // 處理反射或業(yè)務異常(實際項目中可轉(zhuǎn)為自定義異常)
            throw new RuntimeException("方法執(zhí)行失敗:" + e.getMessage(), e);
        }
    }
}

步驟 4:測試日志注解效果

public class LogTest {
    public static void main(String[] args) {
        OrderService orderService = new OrderService();
        
        // 測試正常創(chuàng)建訂單
        LogAnnotationProcessor.executeWithLog(orderService, "createOrder", "user_001", 99.9);
        
        // 測試取消訂單
        LogAnnotationProcessor.executeWithLog(orderService, "cancelOrder", "ORDER_123456");
        
        // 測試異常場景(金額為負數(shù))
        try {
            LogAnnotationProcessor.executeWithLog(orderService, "createOrder", "user_002", -10.0);
        } catch (Exception e) {
            // 此處僅捕獲,不處理(異常已被日志記錄)
        }
    }
}

執(zhí)行結(jié)果

===== 【日志開始】創(chuàng)建訂單 =====
參數(shù):[user_001, 99.9]
耗時:101ms
返回值:ORDER_1698765432100
===== 【日志結(jié)束】創(chuàng)建訂單 =====

===== 【日志開始】取消訂單 =====
參數(shù):[ORDER_123456]
耗時:50ms
===== 【日志結(jié)束】取消訂單 =====

===== 【日志開始】創(chuàng)建訂單 =====
參數(shù):[user_002, -10.0]
執(zhí)行異常:金額必須大于0
===== 【日志結(jié)束】創(chuàng)建訂單 =====

日志注解執(zhí)行流程圖解

(注:核心展示流程節(jié)點:調(diào)用方法→反射檢查注解→記錄開始日志→執(zhí)行方法→記錄結(jié)束日志)

案例二:自定義參數(shù)校驗注解 —— 告別重復的 if-else

需求分析

我們需要兩個注解:

  • @NotNull:標記參數(shù)不能為 null;
  • @Range:標記數(shù)值參數(shù)必須在指定范圍內(nèi)(如年齡 1-120 歲)。

實現(xiàn)效果:方法調(diào)用時自動校驗參數(shù),不符合規(guī)則則拋出明確的異常信息。

步驟 1:定義校驗注解

1.1@NotNull注解

import java.lang.annotation.*;

@Target(ElementType.PARAMETER) // 僅用于方法參數(shù)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
    // 校驗失敗的提示信息
    String message() default "參數(shù)不能為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 "參數(shù)不在指定范圍內(nèi)";
}

步驟 2:創(chuàng)建使用校驗注解的業(yè)務類

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 = "積分不能為負數(shù)") int points) {
        System.out.println("更新積分成功:userId=" + userId + ", points=" + points);
    }
}

步驟 3:實現(xiàn)參數(shù)校驗器(核心邏輯)

通過反射獲取方法參數(shù)上的注解,逐個校驗參數(shù)是否符合注解規(guī)則:

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

public class ParamValidator {

    /**
     * 校驗方法參數(shù)是否符合注解規(guī)則
     * @param target 目標對象
     * @param methodName 方法名
     * @param args 方法參數(shù)
     * @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. 獲取方法參數(shù)列表(包含注解信息)
        Parameter[] parameters = method.getParameters();

        // 3. 遍歷參數(shù),執(zhí)行校驗
        for (int i = 0; i < parameters.length; i++) {
            Parameter param = parameters[i];
            Object arg = args[i]; // 當前參數(shù)值

            // 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注解(僅對數(shù)值類型生效)
            if (param.isAnnotationPresent(Range.class) && arg instanceof Number) {
                Range range = param.getAnnotation(Range.class);
                long value = ((Number) arg).longValue(); // 轉(zhuǎn)為long統(tǒng)一處理
                if (value < range.min() || value > range.max()) {
                    throw new IllegalArgumentException(range.message());
                }
            }
        }

        // 4. 校驗通過,執(zhí)行方法
        method.invoke(target, args);
    }
}

步驟 4:測試參數(shù)校驗效果

public class ValidatorTest {
    public static void main(String[] args) {
        UserService userService = new UserService();
        
        // 測試正常參數(shù)
        try {
            ParamValidator.validate(userService, "addUser", "user_001", 25);
        } catch (Exception e) {
            System.out.println("錯誤:" + e.getMessage());
        }
        
        // 測試null參數(shù)(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());
        }
        
        // 測試積分負數(shù)
        try {
            ParamValidator.validate(userService, "updatePoints", "user_003", -10);
        } catch (Exception e) {
            System.out.println("錯誤:" + e.getMessage());
        }
    }
}

執(zhí)行結(jié)果

新增用戶成功:userId=user_001, age=25
錯誤:用戶ID不能為空
錯誤:年齡必須在1-120歲之間
錯誤:積分不能為負數(shù)

參數(shù)校驗流程圖解

實戰(zhàn)總結(jié):自定義注解的設(shè)計原則與優(yōu)化方向

1. 注解設(shè)計三要素

  • 明確作用范圍:用@Target嚴格限制注解的使用場景(如日志注解只用于方法);
  • 合理生命周期:需要反射解析的注解必須用@Retention(RUNTIME)
  • 屬性精簡實用:只保留必要的屬性(如日志注解的description、校驗注解的message),并設(shè)置合理默認值。

2. 反射解析優(yōu)化技巧

  • 緩存反射結(jié)果Method、Parameter等對象的獲取有性能開銷,可通過ConcurrentHashMap緩存注解與方法的映射關(guān)系;
  • 批量處理注解:用getAnnotations()一次性獲取所有注解,避免多次反射調(diào)用;
  • 異常友好提示:校驗或日志失敗時,異常信息要明確(如 "年齡必須在 1-120 歲之間" 而非 "參數(shù)錯誤")。

3. 與現(xiàn)有框架的對比

  • 日志注解:Spring 的@Log、Lombok 的@Log4j2功能更完善,但自定義注解可靈活適配業(yè)務需求(如對接特定日志系統(tǒng));
  • 參數(shù)校驗:JSR-303 規(guī)范(javax.validation)提供了豐富的校驗注解,但自定義注解可實現(xiàn)框架不支持的特殊校驗(如手機號格式、身份證號規(guī)則)。

結(jié)語:注解 + 反射 = 代碼的 "隱形翅膀"

通過本文的兩個實戰(zhàn)案例,你應該體會到:

  • 注解是 "聲明式編程" 的載體,讓代碼更簡潔、意圖更明確;
  • 反射是注解的 "執(zhí)行引擎",讓標記轉(zhuǎn)化為實際邏輯。

這對組合在框架開發(fā)(如 Spring、MyBatis)中無處不在,掌握它們能讓你從 "使用者" 升級為 "設(shè)計者"。

動手練習:嘗試擴展本文案例 ——

  1. 給日志注解增加level屬性(INFO/ERROR),實現(xiàn)不同級別日志的輸出;
  2. 新增@Pattern注解,校驗字符串是否符合正則表達式(如手機號、郵箱)。

總結(jié)

到此這篇關(guān)于Java注解與反射實戰(zhàn)之實現(xiàn)自定義日志與參數(shù)校驗注解的文章就介紹到這了,更多相關(guān)Java自定義日志與參數(shù)校驗注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java+EasyExcel實現(xiàn)文件上傳功能

    Java+EasyExcel實現(xiàn)文件上傳功能

    這篇文章主要為大家詳細介紹了如何通過Java和EasyExcel實現(xiàn)文件上傳功能,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2023-02-02
  • SpringBoot實現(xiàn)公共字段自動填充的方法步驟

    SpringBoot實現(xiàn)公共字段自動填充的方法步驟

    這篇文章主要介紹了SpringBoot實現(xiàn)公共字段自動填充的方法步驟,文中通過代碼示例講解的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下
    2024-11-11
  • Spring Boot整合logback一個簡單的日志集成架構(gòu)

    Spring Boot整合logback一個簡單的日志集成架構(gòu)

    今天小編就為大家分享一篇關(guān)于Spring Boot整合logback一個簡單的日志集成架構(gòu),小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • Java字符拼接成字符串的注意點詳解

    Java字符拼接成字符串的注意點詳解

    這篇文章主要介紹了Java字符拼接成字符串的注意點詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-07-07
  • Spring Cloud Gateway內(nèi)置的斷言和過濾器作用說明

    Spring Cloud Gateway內(nèi)置的斷言和過濾器作用說明

    這篇文章主要介紹了Spring Cloud Gateway內(nèi)置的斷言和過濾器作用說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • Java超詳細教你寫一個學籍管理系統(tǒng)案例

    Java超詳細教你寫一個學籍管理系統(tǒng)案例

    這篇文章主要介紹了怎么用Java來寫一個學籍管理系統(tǒng),學籍管理主要涉及到學生信息的增刪查改,本篇將詳細的實現(xiàn),感興趣的朋友跟隨文章往下看看吧
    2022-03-03
  • Eclipse智能提示及快捷鍵

    Eclipse智能提示及快捷鍵

    本文主要介紹了Eclipse智能提示及快捷鍵的相關(guān)知識,具有很好的參考價值。下面跟著小編一起來看下吧
    2017-03-03
  • Java實現(xiàn)文件圖片的預覽和下載功能

    Java實現(xiàn)文件圖片的預覽和下載功能

    這篇文章主要為大家詳細介紹了如何使用Java實現(xiàn)文件圖片的預覽和下載功能,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2025-04-04
  • MyBatis學習教程之開發(fā)Dao的方法教程

    MyBatis學習教程之開發(fā)Dao的方法教程

    這篇文章主要給大家介紹了關(guān)于MyBatis開發(fā)Dao的相關(guān)資料,使用Mybatis開發(fā)Dao,通常有兩個方法,即原始Dao開發(fā)方法和Mapper接口開發(fā)方法。文中通過示例代碼介紹的非常詳細,需要的朋友們下面來一起看看吧。
    2017-07-07
  • Spring?@Transactional?注解從入門到避坑指南

    Spring?@Transactional?注解從入門到避坑指南

    文章主要講解了Spring的@Transactional注解的使用場景、核心屬性詳解、事務傳播行為、隔離級別、回滾配置、底層原理以及失效場景和避免之道,并還給出了最佳實踐,感興趣的朋友跟隨小編一起看看吧
    2026-05-05

最新評論

邯郸县| 揭东县| 常熟市| 丹阳市| 化州市| 黑龙江省| 于田县| 兰考县| 合肥市| 大余县| 遵义县| 聂拉木县| 浠水县| 锡林浩特市| 乾安县| 长沙县| 房山区| 临夏市| 通州市| 元江| 瑞金市| 樟树市| 岱山县| 南宁市| 高要市| 滁州市| 大厂| 绍兴县| 堆龙德庆县| 梓潼县| 聂荣县| 桃园市| 菏泽市| 江阴市| 新泰市| 南康市| 白银市| 昆山市| 临桂县| 吉水县| 万州区|