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; } }
使用方式:在对应的方法上加上此注解 即可享用!