spring mvc项目通过aop打印日志(基于AspectJ实现)

说明

初次了解AOP就自己基于AspectJ实现了一个打印日志的功能,后来才发现Spring也有相关的接口,想用的话还是使用Spring提供的接口吧,我的这套代码还是有很多不足之处,比如对每个切点添加操作都要直接修改原来的代码,还是不够优雅。

简述

平常我们打印日志的时候需要在每一个地方使用logger打印,aop提供了一种面向切面的方式,不需要在每一个地方都写一行代码,而是通过配置切面在我们需要执行的函数前后获得切点,在切点处直接执行相应的方法。话不多说,让我们一起来使用吧。

使用简述

本次使用的项目是基于Spring MVC的web项目,项目必须依赖spring,同时本项目基于注解配置了aop,当然你也可以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>

相关代码

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

/**
 * 控制切面的自定义操作,
 * 使用方式如下(基于注解):
 * 1. 重写pointCut方法并定义切点
 * 2. 重写对应方法方法进入执行内容
 * 使用方式如下(基于xml):
 * 1. 继承该类
 * 2. 配置切点
 * 3. 配置wrap方法为对应的before、after等
 */
@SuppressWarnings("unused")
public abstract class AopConfiguration implements ILoggerInfoHandler, IAopConfiguration {
    //    不发出警告的程序最大执行时间,单位ms
    private long timeThreshold;
//    日志打印的标签
    private String tag;

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

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

    /**
     *  需要重写并定义切点
     */
    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函数
     */
//    @Before("pointCutMethod()")
    public final void wrapBefore(JoinPoint joinPoint) {
        String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        Object[] args = joinPoint.getArgs();
        doBefore(joinPoint, parameterNames, args);
    }

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

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

    /**
     * 包裹around函数
     * @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;

//        执行切点
        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);
    }

    /**
     * 如果结果是非常长的list,就要截取一部分打印到日志
     * @param resultValue
     * @return
     */
    @SuppressWarnings("unchecked")
    protected Object getResultToAopResult(final Object resultValue) {
//        如果结果太长默认只取三条
        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) {
//                如果结果的长度大于maxSize,并且字符串长度大于maxLength
//                就取出其中的maxSize条数据打印在日志
                aopResult.setType(resultValue.getClass().getSimpleName());
                aopResult.setExplain("截取" + maxSize + "条结果展示!");
                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是一个打印日志的示例,您只需要修改@Pointcut里面的内容,如下文中的实例com.ninggc.template.springbootfastdemo.web.controller.*..*(..)是指controller包下的所有类的所有方法,将其修改为自己的包即可。

/**
 * 控制controller的函数的入口和出口处打印日志
 */
@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 {
//    自定义输出日志的标签
    String getTag();
//    自定义不发出警告的程序最大执行时间,单位ms,默认未300
    default Long getTimeThreshold() {
        return 500L;
    }
}

以上四个文件是全部的文件,以下是具体的代码流程描述

AopConfiguration使用详述

//第一类方法 wrap*方法

wrapBefore
wrapAfterReturn
wrapAfterThrow
wrapAround
//以上四个wrap*的方法是相应的切点处执行的方法,在这个实例中只在wrapAround上注解了@around,没有涉及到其他三个方法

//第二类方法 do*方法

doBefore
doAfterReturn
doAfterThrow
doAround
//wrap*方法调用了do*方法,do*方法是我打印日志的格式,内部调用了更具体的方法,见第三类方法

//第三类方法,这是继承类可以改写的方法,方法功能如方法名所示

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;

//        执行切点
        try {
            doBefore(joinPoint, parameterNames, args); //内部调用了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); //内部调用了afterReturn
        } catch (Exception e) {
            doAfterThrow(joinPoint, e); //内部调用了afterThrow
            throw e;
        }
        return joinPoint.proceed(args);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容