spring mvc項(xiàng)目通過aop打印日志(基于AspectJ實(shí)現(xiàn))

說明

初次了解AOP就自己基于AspectJ實(shí)現(xiàn)了一個(gè)打印日志的功能狰贯,后來才發(fā)現(xiàn)Spring也有相關(guān)的接口,想用的話還是使用Spring提供的接口吧,我的這套代碼還是有很多不足之處葫掉,比如對每個(gè)切點(diǎn)添加操作都要直接修改原來的代碼目尖,還是不夠優(yōu)雅。

簡述

平常我們打印日志的時(shí)候需要在每一個(gè)地方使用logger打印勇垛,aop提供了一種面向切面的方式脖母,不需要在每一個(gè)地方都寫一行代碼,而是通過配置切面在我們需要執(zhí)行的函數(shù)前后獲得切點(diǎn)闲孤,在切點(diǎn)處直接執(zhí)行相應(yīng)的方法谆级。話不多說,讓我們一起來使用吧讼积。

使用簡述

本次使用的項(xiàng)目是基于Spring MVC的web項(xiàng)目肥照,項(xiàng)目必須依賴spring,同時(shí)本項(xiàng)目基于注解配置了aop勤众,當(dāng)然你也可以xml配置aop舆绎。

Maven配置

<dependencies>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

相關(guān)代碼

AopConfiguration是核心,使用方式見ControllerAopConfigurationImpl文件

/**
 * 控制切面的自定義操作们颜,
 * 使用方式如下(基于注解):
 * 1. 重寫pointCut方法并定義切點(diǎn)
 * 2. 重寫對應(yīng)方法方法進(jìn)入執(zhí)行內(nèi)容
 * 使用方式如下(基于xml):
 * 1. 繼承該類
 * 2. 配置切點(diǎn)
 * 3. 配置wrap方法為對應(yīng)的before吕朵、after等
 */
@SuppressWarnings("unused")
public abstract class AopConfiguration implements ILoggerInfoHandler, IAopConfiguration {
    //    不發(fā)出警告的程序最大執(zhí)行時(shí)間,單位ms
    private long timeThreshold;
//    日志打印的標(biāo)簽
    private String tag;

    public AopConfiguration() {
        timeThreshold = getTimeThreshold();
        tag = getTag();
    }

    private Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
    protected Gson gson = new Gson();

    /**
     *  需要重寫并定義切點(diǎn)
     */
    protected abstract void pointCutMethod();

    @Data
    private static class AopResult {
        private String explain;
        private String type;
        private Integer totalSize;
        private Integer totalLength;
        private List<Object> subList;
    }

    /**
     * 包裹before函數(shù)
     */
//    @Before("pointCutMethod()")
    public final void wrapBefore(JoinPoint joinPoint) {
        String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        Object[] args = joinPoint.getArgs();
        doBefore(joinPoint, parameterNames, args);
    }

    /**
     * 包裹afterReturn函數(shù)
     */
//    @AfterReturning(value = "pointCutMethod()", returning = "returnValue")
    public final Object wrapAfterReturn(JoinPoint joinPoint, Object returnValue) {
        return doAfterReturn(joinPoint, returnValue);
    }

    /**
     * 包裹afterThrow函數(shù)
     */
//    @AfterThrowing(value = "pointCutMethod()", throwing = "exception")
    public final void wrapAfterThrow(JoinPoint joinPoint, Exception exception) throws Exception {
        doAfterThrow(joinPoint, exception);
    }

    /**
     * 包裹around函數(shù)
     * @return
     */
//    @Around(value = "pointCutMethod() && @annotation(methodLog)", argNames = "joinPoint")
    @Around(value = "pointCutMethod()", argNames = "joinPoint")
    private Object wrapAround(ProceedingJoinPoint joinPoint) throws Throwable {
        String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        Object[] args = joinPoint.getArgs();
        return doAround(joinPoint, parameterNames, args);
    }

    private void doBefore(JoinPoint joinPoint, String[] parameterNames, Object[] args) {
        before(joinPoint, parameterNames, args);
        String classAndMethodName = joinPoint.getSignature().toShortString();

        String logContent = "before " + tag + " execute: " + classAndMethodName + "\t" +
                gson.toJson(parameterNames) + " --> " + gson.toJson(args);
        log(logContent);
    }

    private Object doAfterReturn(JoinPoint joinPoint, final Object returnValue) {
        afterReturn(joinPoint, returnValue);
        String classAndMethodName = joinPoint.getSignature().toShortString();

        String logContent = "after " + tag + " execute: " + classAndMethodName + "\t"
                + "result --> " + gson.toJson(getResultToAopResult(returnValue));
        log(logContent);
        return returnValue;
    }

    private void doAfterThrow(JoinPoint joinPoint, Exception exception) throws Exception {
        afterThrow(joinPoint, exception);
        String classAndMethodName = joinPoint.getSignature().toShortString();
        String logContent = "after " + tag + " execute: " + classAndMethodName + "\t"
                + "throw --> " + gson.toJson(exception.getMessage());
        log(logContent);
        throw exception;
    }

    private Object doAround(ProceedingJoinPoint joinPoint, String[] parameterNames, Object[] args) throws Throwable {
        String classAndMethodName = joinPoint.getSignature().toShortString();
        Object returnValue = null;

//        執(zhí)行切點(diǎn)
        try {
            doBefore(joinPoint, parameterNames, args);

            long start = System.currentTimeMillis();
            returnValue = joinPoint.proceed(args);
            long duration = System.currentTimeMillis() - start;
            if (duration > timeThreshold) {
                warn( classAndMethodName + " execute time is too long: " + " --> " + duration + " ms");
            } else {
                log(classAndMethodName + " execute time: " + " --> " + duration + " ms");
            }

            doAfterReturn(joinPoint, returnValue);
        } catch (Exception e) {
            doAfterThrow(joinPoint, e);
            afterThrow(joinPoint, e);
            throw e;
        }
        return joinPoint.proceed(args);
    }

    /**
     * 如果結(jié)果是非常長的list窥突,就要截取一部分打印到日志
     * @param resultValue
     * @return
     */
    @SuppressWarnings("unchecked")
    protected Object getResultToAopResult(final Object resultValue) {
//        如果結(jié)果太長默認(rèn)只取三條
        final int maxSize = 3;
        final int maxLength = 300;
        AopResult aopResult = new AopResult();
        if (resultValue instanceof Collection) {
            Collection<Object> collection = (Collection<Object>) resultValue;
            int length = gson.toJson(collection).length();
            if (collection.size() > maxSize && length > maxLength) {
//                如果結(jié)果的長度大于maxSize努溃,并且字符串長度大于maxLength
//                就取出其中的maxSize條數(shù)據(jù)打印在日志
                aopResult.setType(resultValue.getClass().getSimpleName());
                aopResult.setExplain("截取" + maxSize + "條結(jié)果展示!");
                aopResult.setTotalSize(collection.size());
                aopResult.setTotalLength(length);
                aopResult.setSubList(Arrays.asList(collection.toArray()).subList(0, maxSize));
                return aopResult;
            }
        }
        return resultValue;
    }

    protected void log(String content) {
        logger.info(content);
    }

    protected void warn(String content) {
        logger.warn(content);
    }

    protected void error(String content) {
        logger.error(content);
    }
}

ControllerAopConfigurationImpl是一個(gè)打印日志的示例阻问,您只需要修改@Pointcut里面的內(nèi)容梧税,如下文中的實(shí)例com.ninggc.template.springbootfastdemo.web.controller.*..*(..)是指controller包下的所有類的所有方法蛇数,將其修改為自己的包即可甸鸟。

/**
 * 控制controller的函數(shù)的入口和出口處打印日志
 */
@Component
@Aspect
public class ControllerAopConfigurationImpl extends AopConfiguration {
    @Pointcut("execution(* com.ninggc.template.springbootfastdemo.web.controller.*..*(..))")
    @Override
    protected void pointCutMethod() { }

    @Override
    public String getTag() {
        return "controller";
    }

    @Override
    public void before(JoinPoint joinPoint, String[] parameterNames, Object[] args) {
    }

    @Override
    public void afterReturn(JoinPoint joinPoint, Object returnValue) {
    }

    @Override
    public void afterThrow(JoinPoint joinPoint, Exception exception) throws Exception {
    }
}

其他的需要的文件

public interface IAopConfiguration {
    void before(JoinPoint joinPoint, String[] parameterNames, Object[] args);

    void afterReturn(JoinPoint joinPoint, final Object returnValue);

    void afterThrow(JoinPoint joinPoint, Exception exception) throws Exception;
}
public interface ILoggerInfoHandler {
//    自定義輸出日志的標(biāo)簽
    String getTag();
//    自定義不發(fā)出警告的程序最大執(zhí)行時(shí)間,單位ms蘑志,默認(rèn)未300
    default Long getTimeThreshold() {
        return 500L;
    }
}

以上四個(gè)文件是全部的文件煌茬,以下是具體的代碼流程描述

AopConfiguration使用詳述

//第一類方法 wrap*方法

wrapBefore
wrapAfterReturn
wrapAfterThrow
wrapAround
//以上四個(gè)wrap*的方法是相應(yīng)的切點(diǎn)處執(zhí)行的方法斥铺,在這個(gè)實(shí)例中只在wrapAround上注解了@around,沒有涉及到其他三個(gè)方法

//第二類方法 do*方法

doBefore
doAfterReturn
doAfterThrow
doAround
//wrap*方法調(diào)用了do*方法坛善,do*方法是我打印日志的格式晾蜘,內(nèi)部調(diào)用了更具體的方法邻眷,見第三類方法

//第三類方法,這是繼承類可以改寫的方法剔交,方法功能如方法名所示

public interface IAopConfiguration {
    void before(JoinPoint joinPoint, String[] parameterNames, Object[] args);

    void afterReturn(JoinPoint joinPoint, final Object returnValue);

    void afterThrow(JoinPoint joinPoint, Exception exception) throws Exception;
}

//打印日志的具體流程如下所示

    private Object doAround(ProceedingJoinPoint joinPoint, String[] parameterNames, Object[] args) throws Throwable {
        String classAndMethodName = joinPoint.getSignature().toShortString();
        Object returnValue = null;

//        執(zhí)行切點(diǎn)
        try {
            doBefore(joinPoint, parameterNames, args); //內(nèi)部調(diào)用了before

            long start = System.currentTimeMillis();
            returnValue = joinPoint.proceed(args);
            long duration = System.currentTimeMillis() - start;
            if (duration > timeThreshold) {
                warn( classAndMethodName + " execute time is too long: " + " --> " + duration + " ms");
            } else {
                log(classAndMethodName + " execute time: " + " --> " + duration + " ms");
            }

            doAfterReturn(joinPoint, returnValue); //內(nèi)部調(diào)用了afterReturn
        } catch (Exception e) {
            doAfterThrow(joinPoint, e); //內(nèi)部調(diào)用了afterThrow
            throw e;
        }
        return joinPoint.proceed(args);
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肆饶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子岖常,更是在濱河造成了極大的恐慌驯镊,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竭鞍,死亡現(xiàn)場離奇詭異板惑,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)偎快,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門冯乘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人晒夹,你說我怎么就攤上這事裆馒。” “怎么了丐怯?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵喷好,是天一觀的道長。 經(jīng)常有香客問我读跷,道長梗搅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任舔亭,我火速辦了婚禮些膨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钦铺。我一直安慰自己订雾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布矛洞。 她就那樣靜靜地躺著洼哎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪沼本。 梳的紋絲不亂的頭發(fā)上噩峦,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機(jī)與錄音抽兆,去河邊找鬼识补。 笑死,一個(gè)胖子當(dāng)著我的面吹牛辫红,可吹牛的內(nèi)容都是我干的凭涂。 我是一名探鬼主播祝辣,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼切油!你這毒婦竟也來了蝙斜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤澎胡,失蹤者是張志新(化名)和其女友劉穎孕荠,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體攻谁,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡稚伍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巢株。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片槐瑞。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖阁苞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情祠挫,我是刑警寧澤那槽,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站等舔,受9級特大地震影響骚灸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜慌植,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一甚牲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蝶柿,春花似錦丈钙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至芙扎,卻和暖如春星岗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背戒洼。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工俏橘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人圈浇。 一個(gè)月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓寥掐,卻偏偏與公主長得像靴寂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子曹仗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容