首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring AOP 自定義註解實現靈活日志管理:從切面設計到數據庫落地

     編輯:Java 綜合教學

告別硬編碼日志:用 @Log 註解 + AOP 實現方法級操作審計、異常捕獲、性能監控

傳統在業務代碼中手動調用 log.info() 既侵入性強又難以統一。本文展示如何通過自定義註解 @Log + Spring AOP 切面,實現對 Controller 層方法的無侵入式日志記錄:操作類型、操作名稱、耗時、請求參數、異常堆棧、請求 IP、操作人,自動入庫數據庫,並支持控制台輸出與本地文件雙通道。

一、 整體架構設計

  • 註解定義:@Log(operationType, operationName) 標記在 Controller 方法上。
  • 切面類:SystemLogAspect 定義切點、前置/環繞/後置/異常/返回通知。
  • 數據模型:SystemLog 實體映射日志表字段。
  • 持久層:SystemLogMapper (MyBatis) + SystemLogService 完成入庫。
  • 配置啟用:<aop:aspectj-autoproxy proxy-target-class="true"/> 強制 CGLIB 代理。

二、 核心代碼實現

1. 自定義註解 @Log

@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
    String operationType() default ""; // 如 "add操作:"
    String operationName() default ""; // 如 "添加用戶"
}

2. 切面類 SystemLogAspect(核心邏輯)

@Aspect
@Component
public class SystemLogAspect {
    @Resource private SystemLogService systemLogService;
    private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);

    // Controller 層切點
    @Pointcut("execution(* com.gcx.controller..*.*(..))")
    public void controllerAspect() {}

    // 環繞通知:計算耗時
    @Around("controllerAspect()")
    public void around(JoinPoint jp) {
        long start = System.currentTimeMillis();
        try { ((ProceedingJoinPoint) jp).proceed(); }
        finally {
            long cost = System.currentTimeMillis() - start;
            logger.info("around " + jp + "\tUse time: " + cost + " ms");
        }
    }

    // 後置通知:記錄正常操作日志(核心業務邏輯)
    @After("controllerAspect()")
    public void after(JoinPoint jp) {
        // 1. 解析註解信息
        Method method = resolveMethod(jp);
        Log logAnn = method.getAnnotation(Log.class);
        String opType = logAnn != null ? logAnn.operationType() : "";
        String opName = logAnn != null ? logAnn.operationName() : "";

        // 2. 構建日志實體(簡化:模擬用戶/IP)
        SystemLog log = new SystemLog();
        log.setId(UUID.randomUUID().toString());
        log.setDescription(opName);
        log.setMethod(jp.getTarget().getClass().getName() + "." + jp.getSignature().getName() + "()" + opType);
        log.setLogType(0L); // 正常
        log.setRequestIp("127.0.0.1");
        log.setCreateBy("張三");
        log.setCreateDate(new Date());

        // 3. 入庫
        systemLogService.insert(log);
    }

    // 異常通知:記錄異常日志
    @AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint jp, Throwable e) {
        // 解析參數序列化為 JSON
        String params = Arrays.stream(jp.getArgs())
            .map(JsonUtil::getJsonStr).collect(Collectors.joining(";"));

        SystemLog log = new SystemLog();
        log.setId(UUID.randomUUID().toString());
        log.setExceptioncode(e.getClass().getName());
        log.setExceptionDetail(e.getMessage());
        log.setParams(params);
        log.setLogType(1L); // 異常
        // ... 其他字段同 after()
        systemLogService.insert(log);
    }

    // 輔助方法:精確匹配方法簽名(含參數類型)
    private Method resolveMethod(JoinPoint jp) throws Exception {
        String className = jp.getTarget().getClass().getName();
        String methodName = jp.getSignature().getName();
        Object[] args = jp.getArgs();
        Class clazz = Class.forName(className);
        for (Method m : clazz.getMethods()) {
            if (m.getName().equals(methodName) && m.getParameterTypes().length == args.length) {
                return m;
            }
        }
        throw new NoSuchMethodException();
    }
}

3. Controller 使用示例

@Controller
@RequestMapping("userController")
public class UserController {
    @Autowired private UserService userService;

    @RequestMapping("testAOP")
    @Log(operationType = "add操作:", operationName = "添加用戶")
    public void testAOP(String userName, String password) {
        userService.addUser(userName, password);
    }
}

4. Spring 配置關鍵點

<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="systemLogAspect" class="com.gcx.annotation.SystemLogAspect"/>
<context:component-scan base-package="com.gcx"/>

三、 運行效果與數據庫記錄

控制台輸出 數據庫日志表

控制台輸出包含:請求方法、方法描述、請求人、請求 IP、耗時、異常堆棧等。數據庫表 system_log 完整保存所有字段,便於後續審計查詢、報表分析。

四、 進階優化建議

  1. 異步入庫:@Async 或線程池異步保存日志,避免阻塞主業務線程。
  2. 參數脫敏:密碼、身份證、銀行卡等敏感字段在 JSON 序列化前脫敏(如 ***)。
  3. 分布式追蹤:引入 TraceId (如 SkyWalking、Zipkin),關聯全鏈路日志。
  4. 切點擴展:可增加 Service 層切點專門記錄異常,Controller 層專記業務操作,職責更清晰。
  5. 性能優化:緩存 Method 對象、註解對象,避免每次反射解析。

五、 總結

「註解 + AOP」模式實現了橫切關注點與業務邏輯完全解耦,是 Spring 生態中實現日志審計、權限控制、事務管理、緩存處理的標準化範式。掌握此模式,可輕松應對各類「無感知業務增強」需求。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved