Spring++:AOP 拦截 Service | Controller 日志

首先我们为什么需要做日志管理,在现实的上线中我们经常会遇到系统出现异常或者问题。

这个时候就马上打开CRT或者SSH连上服务器拿日子来分析。受网络的各种限制。于是我们就想为什么不能直接在管理后台查看报错的信息呢。

于是日志管理就出现了:↓

引入相关依赖:

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

第一步定义两个注解:

/**
 * 自定义注解 拦截service
 *
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemServiceLog {
    String description() default "";
}
/**
 * 自定义注解 拦截Controller
 *
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemControllerLog {
    String description() default "";
}

第二步创建一个切点类:

package com.mi.mlq.aoplog;

import com.alibaba.fastjson.JSON;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;

/**
 * 切点类
 *
 * @version 1.0
 * @since 2020年12月15日16:58:59
 */
@Aspect
@Component
public class ServiceLogAspect {

    /**
     * 本地异常日志记录对象
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(ServiceLogAspect.class);
    private static final ThreadLocal<Long> START_TIME_THREAD_LOCAL = new NamedThreadLocal<>("ThreadLocal StartTime");

    /**
     * Controller层切点
     */
    @Pointcut("@annotation(com.mi.mlq.aoplog.SystemServiceLog)")
    public void serviceAspect() {
    }

    /**
     * 前置通知 用于拦截Controller层记录用户的操作
     *
     * @param joinPoint 切点
     */
    @Before("serviceAspect()")
    public void doBefore(JoinPoint joinPoint) {
        // 线程绑定变量(该数据只有当前请求的线程可见)
        START_TIME_THREAD_LOCAL.set(System.currentTimeMillis());
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        String params = "";
        if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
            for (int i = 0; i < joinPoint.getArgs().length; i++) {
                params += JSON.toJSONString(joinPoint.getArgs()[i]) + ";";
            }
        }
        LOGGER.info("[" + classMethod + "]" + "[params={}]", params);
    }

    @AfterReturning(pointcut = "serviceAspect()")
    public void doAfterReturning(JoinPoint joinPoint) {
        // 1、得到线程绑定的局部变量(开始时间)
        long beginTime = START_TIME_THREAD_LOCAL.get();
        // 2、结束时间
        long endTime = System.currentTimeMillis();
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        LOGGER.info("[" + classMethod + "]" + "[计时结束:{}  耗时:{}ms 最大内存: {}m  已分配内存: {}m  已分配内存中的剩余空间: {}m  最大可用内存: {}m]",
                new SimpleDateFormat("YYYY-MM-DD hh:mm:ss.SSS").format(endTime), (endTime - beginTime),
                Runtime.getRuntime().maxMemory() / 1024 / 1024,
                Runtime.getRuntime().totalMemory() / 1024 / 1024, Runtime.getRuntime().freeMemory() / 1024 / 1024,
                (Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory() + Runtime.getRuntime().freeMemory()) / 1024 / 1024);
    }

    /**
     * 获取注解中对方法的描述信息 用于service层注解
     *
     * @param joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */
    public static String getServiceMthodDescription(JoinPoint joinPoint)
            throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    description = method.getAnnotation(SystemServiceLog.class).description();
                    break;
                }
            }
        }
        return description;
    }

}
package com.mi.mlq.aoplog;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * 切点类
 *
 * @version 1.0
 * @since 2020年12月15日16:58:59
 */
@Aspect
@Component
public class ControllerLogAspect {

    /**
     * 本地异常日志记录对象
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(ControllerLogAspect.class);
    private static final ThreadLocal<Long> START_TIME_THREAD_LOCAL = new NamedThreadLocal<>("ThreadLocal StartTime");

    /**
     * Controller层切点
     */
    @Pointcut("@annotation(com.mi.mlq.aoplog.SystemControllerLog)")
    public void controllerAspect() {
    }

    /**
     * 前置通知 用于拦截Controller层记录用户的操作
     *
     * @param joinPoint 切点
     */
    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint) {
        // 线程绑定变量(该数据只有当前请求的线程可见)
        START_TIME_THREAD_LOCAL.set(System.currentTimeMillis());
        LOGGER.info("doBefore controller logs start...");
        String url = "";
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            url = String.valueOf(request.getRequestURL());
            LOGGER.info("[requestURL]:{}", url);
            LOGGER.info(url + "[remoteAddr]:{}", request.getRemoteAddr());
            LOGGER.info(url + "[remoteHost]:{}", request.getRemoteHost());
            LOGGER.info(url + "[localAddr]:{}", request.getLocalAddr());
            LOGGER.info(url + "[method]:{}", request.getMethod());
            LOGGER.info(url + "[headers]:{}", getHeadersInfo(request));
            LOGGER.info(url + "[parameters]:{}", this.getParamMap(request));
            LOGGER.info(url + "[classMethod]{}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        } catch (Exception e) {
            LOGGER.error(url + "[doBefore controller error={}]", e);
        }
        LOGGER.info(url + "[doBefore controller logs end...]");
    }

    @AfterReturning(returning = "response", pointcut = "controllerAspect()")
    public void doAfterReturning(Object response) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String url = String.valueOf(request.getRequestURL());
        LOGGER.info(url + "[RESPONSE...]: {}", response);
        // 1、得到线程绑定的局部变量(开始时间)
        long beginTime = START_TIME_THREAD_LOCAL.get();
        // 2、结束时间
        long endTime = System.currentTimeMillis();
        LOGGER.info(url + "[计时结束:{}  耗时:{}ms  URI: {}  最大内存: {}m  已分配内存: {}m  已分配内存中的剩余空间: {}m  最大可用内存: {}m]",
                new SimpleDateFormat("yyyy-mm-dd hh:mm:ss.SSS").format(endTime), (endTime - beginTime),
                request.getRequestURI(), Runtime.getRuntime().maxMemory() / 1024 / 1024,
                Runtime.getRuntime().totalMemory() / 1024 / 1024, Runtime.getRuntime().freeMemory() / 1024 / 1024,
                (Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory() + Runtime.getRuntime().freeMemory()) / 1024 / 1024);
    }

    private Map<String, String> getHeadersInfo(HttpServletRequest request) {
        Map<String, String> map = new HashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        return map;
    }

    private Map<String, Object> getParamMap(HttpServletRequest request) {
        Map<String, Object> paramMap = new HashMap<String, Object>();
        Enumeration<?> paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements()) {
            String key = (String) paramNames.nextElement();
            paramMap.put(key, request.getParameter(key));
        }
        return paramMap;
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param joinPoint 切点
     * @return 方法描述
     * @throws Exception
     */
    public static String getControllerMethodDescription(JoinPoint joinPoint) throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    description = method.getAnnotation(SystemControllerLog.class).description();
                    break;
                }
            }
        }
        return description;
    }

}

使用方式:在对应的方法上加上此注解 即可享用!

原文地址:https://www.cnblogs.com/codingmode/p/15293068.html