Spring基於註解的環繞通知實現請求方法日誌記錄

shengshenglalala發表於2020-09-24

使用註解AOP實現方法日誌記錄,免去重複寫日誌儲存的麻煩。該方案能記錄方法請求引數,返回結果等資訊

程式碼結構:

1.自定義註解

package com.example.demo.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 在方法上使用該註解時,Spring會自動儲存被註解方法的請求引數和返回資訊到log表
 * 儲存的基礎欄位包括:
 *      businessItemId:方法名稱
 *      requestParameter:請求的引數名稱以及引數值
 *      responseContent:請求的返回
 *      result:如果程式正常執行則儲存success,否則儲存failure
 *      types:傳參中的logType(根據註解中的傳參決定,非必填)
 *      typesName:types對應的名稱
 *      businessId:傳參中的businessId欄位(根據註解中的傳參決定,非必填)
 *      createTime:建立時間
 *      updateTime:更新時間
 *      note:如果程式異常執行,note欄位儲存e.getMessage()
 * @author shengsheng
 */
// 方法註解
@Target(ElementType.METHOD)
// 執行時可見
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnno {
    /**
     * log的types欄位
     * @return
     */
    String logType() default "";

    /**
     * log的businessId欄位
     * @return
     */
    String businessId() default "";
}

2.切面實現

package com.example.demo.aop;

import com.alibaba.fastjson.JSON;
import com.example.demo.entity.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * Log的AOP實現日誌
 * @author shengsheng
 *
 */
@Component
@Aspect
public class LogAspect {

    /*@Autowired
    private LogMapper logMapper;*/

    @Pointcut("@annotation(com.example.demo.aop.LogAnno)")
    public void logPointCut() {
    }

    /**
     * 環繞通知記錄日誌通過註解匹配到需要增加日誌功能的方法
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("logPointCut()")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        Log log = new Log();
        Signature signature = pjp.getSignature();
        MethodSignature methodSignature = (MethodSignature)signature;
        // 1.方法執行前的處理,相當於前置通知
       // 獲取正在訪問的類
        Class executionClass = pjp.getTarget().getClass();
        // 獲取訪問的方法的名稱
        String methodName = pjp.getSignature().getName();
        // 獲取正在訪問的方法
        Method executionMethod =methodSignature.getMethod();
        // 獲取訪問的方法的引數
        Object[] args = pjp.getArgs();
        //獲取方法中的引數名稱
        ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
        String[] parameterNames = pnd.getParameterNames(executionMethod);
        log.setBusinessItemId(methodName);
        if((parameterNames!=null)&&(parameterNames.length>0)&&(args!=null)&&(args.length>0)){
            Map<String,Object> requestParams = new HashMap<>(args.length);
            for (int i = 0 ;i<parameterNames.length;i++) {
                requestParams.put(parameterNames[i],args[i]);
            }
            //設定請求引數
            log.setRequestParameter(JSON.toJSONString(requestParams));
        }
        LogAnno annotation = executionMethod.getAnnotation(LogAnno.class);
        //獲取註解中的types欄位
        String logTypeStr = annotation.logType();
        if(!StringUtils.isEmpty(logTypeStr)){
            Integer logType = Integer.valueOf(logTypeStr);
            log.setTypes(logType);
            log.setTypesName("");
        }
        String businessId = annotation.businessId();
        log.setBusinessId(businessId);
        Object result = null;
        try {
            //讓代理方法執行
            result = pjp.proceed();
            // 2.後置通知
            log.setResult("success");
        } catch (Exception e) {
            // 3.異常通知
            log.setResult("failure");
            log.setNote(e.getMessage());
        } finally {
            // 4.最終通知
            Date now = new Date();
            log.setCreateTime(now);
            log.setResponseContent(JSON.toJSONString(result));
            log.setUpdateTime(now);
            System.out.println(log);
            //入庫
//            logMapper.insertSelective(log);
        }
        return result;
    }
}

3.在方法上使用註解

package com.example.demo.utils;

import com.example.demo.aop.LogAnno;
import org.springframework.stereotype.Component;

/**
 * description:
 *
 * @author shengsheng
 * @date 2020/9/24 16:32
 */
@Component
public class CalculateUtils {

    //使用註解
    @LogAnno(logType = "1",businessId = "div")
    public Integer div(Integer a,Integer b){
        return a/b;
    }
}

4.測試

package com.example.demo.test;

import com.example.demo.utils.CalculateUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * description:
 *
 * @author shengsheng
 * @date 2020/9/24 16:48
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class CalculateTest {
    @Autowired
    private CalculateUtils calculateUtils;
    @Test
    public void test() {
        Integer div = calculateUtils.div(1, 1);
        System.out.println(div);
    }
}

5.測試結果

Log{id=null, types=1, typesName='', businessId='div', businessItemId='div', result='success', requestParameter='{"a":1,"b":1}', responseContent='1', note='null', createTime=Thu Sep 24 17:12:57 CST 2020, updateTime=Thu Sep 24 17:12:57 CST 2020}

 

相關文章