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

Spring AOP + 註解實現操作日誌自動記錄:從切面定義到 SpEL 動態模板

     編輯:Java 綜合教學

整體架構

  1. 自定義註解 @OperInfo 標註業務方法,支援 SpEL 模板
  2. 切面類 LogAspect 環繞通知攔截,解析註解、構建日誌對象、異步入庫
  3. Spring XML 啟用 AOP,配置切點表達式

1. 定義註解 @OperInfo

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface OperInfo {
    String description() default ""; // SpEL 模板,如 "新增了名為#{#user.username}的用戶"
}

2. 切面實現 LogAspect

public class LogAspect {
    @Resource private OperationLogService operService;

    public Object doAroundMethodForCtLog(ProceedingJoinPoint pjp) throws Throwable {
        Method method = getMethod(pjp);
        if (method != null && method.isAnnotationPresent(OperInfo.class)) {
            OperInfo annotation = method.getAnnotation(OperInfo.class);
            String description = parseSpelTemplate(annotation.description(), pjp);

            HttpServletRequest request = HttpServletUtils.getRequest();
            Users user = (Users) request.getSession().getAttribute("users");

            OperationLog log = new OperationLog();
            log.setOptid(UUIDUtils.getUUID());
            log.setChangeuserid(user.getUsersid());
            log.setChangetime(new Date());
            log.setDescription("用戶:" + user.getUsername() + ",日誌信息:【{" + description + "}】");
            operService.insertService(log); // 建議異步執行
        }
        return pjp.proceed(); // 繼續執行原方法
    }

    /** SpEL 模板解析 */
    private String parseSpelTemplate(String template, ProceedingJoinPoint joinPoint) {
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        Method method = getMethod(joinPoint);
        String[] paramNames = discoverer.getParameterNames(method);

        EvaluationContext context = new StandardEvaluationContext();
        ExpressionParser parser = new SpelExpressionParser();
        Object[] args = joinPoint.getArgs();
        if (args.length == paramNames.length) {
            for (int i = 0; i < args.length; i++) {
                context.setVariable(paramNames[i], args[i]);
            }
        }
        return parser.parseExpression(template, new TemplateParserContext()).getValue(context, String.class);
    }

    private Method getMethod(ProceedingJoinPoint joinPoint) {
        Object target = joinPoint.getTarget();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        try {
            return target.getClass().getMethod(method.getName(), method.getParameterTypes());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

3. Spring AOP 配置

<bean id="logAspect" class="com.aop.LogAspect" />
<aop:config expose-proxy="true">
    <aop:pointcut expression="execution(* com.service.impl.*Impl.*(..))" id="txPointcut"/>
    <aop:aspect ref="logAspect">
        <aop:around method="doAroundMethodForCtLog" pointcut-ref="txPointcut" />
    </aop:aspect>
</aop:config>

4. 業務方法使用範例

@OperInfo(description = "新增了名為#{#user.username}的用戶")
public int insertUser(Users user) {
    // 業務邏輯
    return 1;
}

SpEL 上下文自動包含方法參數 user,可直接存取屬性 #user.username、巢狀屬性 #order.items[0].productName、集合投影 #list.?[price>100] 等高級語法。

關鍵技術點總結

點說明
參數名發現LocalVariableTableParameterNameDiscoverer 需編譯時保留參數名 (-parameters 或 javac -g)
SpEL 安全性模板來自註解(開發者可控),避免外部注入風險
效能考量日誌入庫建議異步 (ThreadPoolTaskExecutor / @Async),避免阻塞主業務
異常處理切面捕獲異常記錄錯誤日誌,但不阻斷原方法異常拋出
擴展性可增加 @OperInfo(type="LOGIN") 分類、支援條件執行 condition="#user.id>100"

進階:結合 @Async 異步持久化

@Async("logExecutor")
public void insertService(OperationLog log) {
    operationLogDao.insert(log);
}

配置 ThreadPoolTaskExecutor bean 名為 logExecutor,核心池 10、隊列 500、拒絕策略 CallerRunsPolicy,確保高峰期不丟日誌、不阻塞業務。

總結

AOP + 註解 + SpEL 實現零侵入、聲明式、動態模板的操作日誌記錄,核心在於參數名發現與異步持久化。掌握此模式,可快速擴展到審計日誌、安全審計、業務指標埋點等場景。

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