【java】使用註解加ASPECT實現攔截方法打印方法日誌

        爲了熟悉AOP中的ASPECTJ的使用,爲了方便我們在寫數據搬運型代碼時候觀測的方便,自己學習並定義了一組切面方法和註解,來實現這個功能,啥都不說了,先上代碼:

首先是註解定義:

import java.lang.annotation.*;

/**
 * @author wangxiao
 * @date 2020-05-26 10:38
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@Documented
public @interface ServiceAroundLog {

    /**
     * 定義方法名稱
     * @return
     */
    String methodName() default "";

    /**
     * 是否打印方法執行時間
     * @return
     */
    boolean isTimeInterval() default true;

    /**
     * 是否打印方法參數
     * @return
     */
    boolean isPrintParam() default true;

}

註解定義完成之後,我們開始定義使用這個註解的切面方法:

import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

/**
 * @author wangxiao
 * @date 2020-05-26 10:50
 */
@Aspect
@Order(2)
@Component
public class ServiceAroundLogAspect {

    @Around("@annotation(ServiceAroundLog)")
    public Object beforeReturnValue(ProceedingJoinPoint point) throws Throwable {
        Method methodSignart = ((MethodSignature) point.getSignature()).getMethod();
        ServiceAroundLog methodAnnotation = methodSignart.getAnnotation(ServiceAroundLog.class);

        if (methodAnnotation != null) {

            Logger logger = LoggerFactory.getLogger(point.getThis().getClass());
            boolean isTimeInterval = methodAnnotation.isTimeInterval();
            String name = methodAnnotation.methodName();
            if (StringUtils.isEmpty(name)) {
                name = methodSignart.getName();
            }
            logger.info("----------方法{}開始執行----------", name);
            long s = 0;
            if (isTimeInterval) {
                s = System.currentTimeMillis();
            }

            boolean isPrintParam = methodAnnotation.isPrintParam();

            if (isPrintParam) {
                Parameter[] parameters = methodSignart.getParameters();
                Object[] args = point.getArgs();
                if (parameters != null || parameters.length > 0) {
                    StringBuilder sb = new StringBuilder();
                    sb.append("方法參數列表:[");
                    for (int i = 0; i < parameters.length; i++) {
                        sb.append("{參數名:");
                        sb.append(parameters[i].getName());
                        sb.append("-");
                        sb.append("值:");
                        sb.append(parameters[i] == null ? "null" : JSON.toJSONString(args[i]));
                        sb.append("}");
                    }
                    sb.append("]");
                    logger.info(sb.toString());
                }
            }

            Object proceed = point.proceed();
            // 返回值爲list類型,打印返回值list數量
            if (proceed instanceof ArrayList) {
                final List list = (List) proceed;
                logger.info("-----返回值爲list集合,大小爲:{}-----", list.size());
            }

            if (isTimeInterval) {
                logger.info("--------方法{}執行耗時:{}ms--------", name, (System.currentTimeMillis() - s));
            }
            logger.info("----------方法{}執行結束----------", name);
            return proceed;
        }

        return point.proceed();
    }
}

這樣當我們在要使用的方法上打上@ServiceAroundLog註解的時候,他就會在方法執行的時候,打印方法執行的日誌了。

例如我們是如上圖這樣使用的。

關於這個AOP的寫法,我也要說一下:

切面的編寫有三種:before,around,after這三種位置,在before和after我們都會get到我們的joinpoint,獲得方法的一些信息,我們可以再方法執行前,方法執行全局和方法執行後做這些事情,之所以我們選擇的是around這個切面層進行日誌的輸出,主要的是有兩點:

        1、around方法既可以獲得到方法執行前的參數,又可以獲得方法執行後的return值。

        2、before和after,一個在執行前,一個在執行後,根本不能測量方法的執行時間,所以只能放在around中。

另外我在這個過程中學習到的東西有:

       1、spring自帶了反射的util,是ReflectionUtils還有代理的util是ProxyUtils,並且如果直接通過反射是拿不到具體的值的,只能通過代理。

       2、before,around,after的一些作用和AOP具體能夠做到什麼的一個認識(使用before可以加全局攔截入口認證,使用around可以攔截方法執行過程並且添加額外的東西,使用after可以定義統一關閉或者還原操作等等)。

       3、真實的AOP的切入點有五種,分別是Before,Around,After,AfterReturning,AfterThrowing,不同情況下會使用到不同的地方,如下圖:

 

        上面的切面,只是我用來學習用的小程序,如果裏面存在什麼問題,歡迎大家予以指正,同時大家也不要輕易用在生產環境的項目裏面,用之前要三思。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章