美文网首页Spring专题(非SpringBoot)
spring mvc项目通过aop打印日志(基于AspectJ实

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

作者: animagus | 来源:发表于2019-09-28 12:46 被阅读0次

    说明

    初次了解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);
        }
    

    相关文章

      网友评论

        本文标题:spring mvc项目通过aop打印日志(基于AspectJ实

        本文链接:https://www.haomeiwen.com/subject/kcsjuctx.html