All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ideaaedi.commonspring.aop.ParameterRecorderAdvice Maven / Gradle / Ivy

There is a newer version: 2100.10.6.LTS17
Show newest version
package com.ideaaedi.commonspring.aop;

import com.alibaba.fastjson2.JSON;
import com.ideaaedi.commonspring.lite.params.ParameterHandleModeEnum;
import com.ideaaedi.commonspring.lite.params.ParameterSerializer;
import com.ideaaedi.commonspring.lite.params.RecordParameters;
import com.ideaaedi.commonspring.spi.RequestPathProvider;
import jakarta.annotation.PostConstruct;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 方法 入参、出参 记录
 * 

* 注: 根据此AOP的逻辑, 若注解与表达式同时匹配成功,那么 注解的优先级高于表达式的优先级。 *

* 特别注意:这里借助了RecordParametersAdvice的logger来记录其它地方的日志。即: 相当于其它地方将记录日志的动 * 作委托给RecordParametersAdvice的logger来进行, 所以此logger需要能打印所有地方最下的日志级别(一般为debug)。 即:需要在配置文件中配置logging.level.com * .ideaaedi.commonspring.aop.ParameterRecorderAdvice=debug * 以保证此处有“权限”记录所有用到的日志级别的日志。 * * @author JustryDeng * @since 2022/6/21 11:38 */ @Aspect public class ParameterRecorderAdvice implements Ordered { public static final String BEAN_NAME = "parameterRecorderAdvice"; /** aop order */ public static int order = Ordered.HIGHEST_PRECEDENCE; private static final Logger log = LoggerFactory.getLogger(ParameterRecorderAdvice.class); /** 无返回值 */ private static final String VOID_STR = void.class.getName(); /** 判断是否是controller类的后缀 */ private static final String CONTROLLER_STR = "Controller"; private static final Class LOG_CLASS = log.getClass(); private static final Map METHOD_MAP = new ConcurrentHashMap<>(8); @Autowired(required = false) private ParameterSerializer parameterSerializer; @Autowired(required = false) private RequestPathProvider requestPathProvider; @Autowired(required = false) private ParameterNameDiscoverer parameterNameDiscoverer; /** * 需要包含的methodReference前缀 *
* 为空则表示全部包含 *
* methodReference形如: com.ideaaedi.commonspring.aop.ParameterRecorderAdvice#init */ private final Set includePrefixes; /** * 需要排除的methodReference前缀 *
* 为空则表示都不需要排除 *
* methodReference形如: com.ideaaedi.commonspring.aop.ParameterRecorderAdvice#init */ private final Set excludePrefixes; /** * 将参数转换为字符串的模式 */ private final ParameterHandleModeEnum handleMode; /** * 打印日志时美化打印 */ private final String prettyPlaceholder; /** * 打印日志时美化打印 */ @SuppressWarnings("rawtypes") private final Set ignoreParamTypesSet; public ParameterRecorderAdvice(Set includePrefixes, Set excludePrefixes) { this(includePrefixes, excludePrefixes, ParameterHandleModeEnum.USE_JSON, true, new Class[]{}); } public ParameterRecorderAdvice(Set includePrefixes, Set excludePrefixes, ParameterHandleModeEnum handleMode, boolean pretty, @SuppressWarnings("rawtypes") Class[] ignoreParamTypes) { this.includePrefixes = includePrefixes == null ? new HashSet<>() : includePrefixes; this.excludePrefixes = excludePrefixes == null ? new HashSet<>() : excludePrefixes; this.handleMode = handleMode; this.prettyPlaceholder = pretty ? "\n" : " "; this.ignoreParamTypesSet = new HashSet<>(); ignoreParamTypesSet.addAll(Arrays.asList(ignoreParamTypes)); } @PostConstruct private void init() throws NoSuchMethodException { String debugStr = RecordParameters.LogLevel.DEBUG.name(); String infoStr = RecordParameters.LogLevel.INFO.name(); String warnStr = RecordParameters.LogLevel.WARN.name(); Method debugMethod = LOG_CLASS.getMethod(debugStr.toLowerCase(), String.class, Object.class); Method infoMethod = LOG_CLASS.getMethod(infoStr.toLowerCase(), String.class, Object.class); Method warnMethod = LOG_CLASS.getMethod(warnStr.toLowerCase(), String.class, Object.class); METHOD_MAP.put(debugStr, debugMethod); METHOD_MAP.put(infoStr, infoMethod); METHOD_MAP.put(warnStr, warnMethod); if (parameterNameDiscoverer == null) { log.info("Set StandardReflectionParameterNameDiscoverer as parameterNameDiscoverer."); parameterNameDiscoverer = new StandardReflectionParameterNameDiscoverer(); } if (handleMode == ParameterHandleModeEnum.CUSTOM_SERIALIZER) { Objects.requireNonNull(parameterSerializer, "parameterSerializer should not be null while parameterHandleMode is set to 'CUSTOM_SERIALIZER'."); } } @Pointcut( "(" + "@within(com.ideaaedi.commonspring.lite.params.RecordParameters)" + " || " + "@annotation(com.ideaaedi.commonspring.lite.params.RecordParameters)" + " || " + "execution(* *..*Controller.*(..))" + ")" + " && " + "!@annotation(com.ideaaedi.commonspring.lite.params.IgnoreRecordParameters)" ) public void executeAdvice() { } /** * 环绕增强 */ @Around("executeAdvice()") public Object aroundAdvice(ProceedingJoinPoint thisJoinPoint) throws Throwable { // 获取目标Class Object targetObj = thisJoinPoint.getTarget(); Class targetClazz = targetObj.getClass(); String clazzName = targetClazz.getName(); // 获取目标method MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature(); Method targetMethod = methodSignature.getMethod(); String methodReference = clazzName + "#" + targetMethod; boolean anyMatchExclude = excludePrefixes.stream().anyMatch(methodReference::startsWith); if (anyMatchExclude) { log.debug("anyMatchExclude is true, skip ParameterRecorderAdvice. methodReference is -> {}", methodReference); return thisJoinPoint.proceed(); } if (!CollectionUtils.isEmpty(includePrefixes)) { boolean anyMatchInclude = includePrefixes.stream().anyMatch(methodReference::startsWith); if (!anyMatchInclude) { log.debug("anyMatchInclude is false, skip ParameterRecorderAdvice. methodReference is -> {}", methodReference); return thisJoinPoint.proceed(); } } // 获取目标annotation RecordParameters annotation = targetMethod.getAnnotation(RecordParameters.class); if (annotation == null) { annotation = targetClazz.getAnnotation(RecordParameters.class); // 如果是通过execution触发的,那么annotation可能为null, 那么给其赋予默认值即可 if (annotation == null && clazzName.endsWith(CONTROLLER_STR)) { annotation = (RecordParameters) AnnotationUtils.getDefaultValue(RecordParameters.class); } } // 是否需要记录入参、出参 boolean shouldRecordInputParams; boolean shouldRecordOutputParams; RecordParameters.LogLevel logLevel; boolean isControllerMethod; if (annotation != null) { shouldRecordInputParams = annotation.strategy() == RecordParameters.Strategy.INPUT || annotation.strategy() == RecordParameters.Strategy.INPUT_OUTPUT; shouldRecordOutputParams = annotation.strategy() == RecordParameters.Strategy.OUTPUT || annotation.strategy() == RecordParameters.Strategy.INPUT_OUTPUT; logLevel = annotation.logLevel(); isControllerMethod = clazzName.endsWith(CONTROLLER_STR); // 此时,若annotation仍然为null, 那说明是通过execution(* *..*Controller.*(..))触发切面的 } else { shouldRecordInputParams = shouldRecordOutputParams = true; logLevel = RecordParameters.LogLevel.INFO; isControllerMethod = true; } final String classMethodInfo = "Class#Method -> " + clazzName + "#" + targetMethod.getName(); if (shouldRecordInputParams) { preHandle(thisJoinPoint, logLevel, targetMethod, classMethodInfo, isControllerMethod); } Object obj = thisJoinPoint.proceed(); if (shouldRecordOutputParams) { postHandle(logLevel, targetMethod, obj, classMethodInfo, isControllerMethod); } return obj; } @Override public int getOrder() { return ParameterRecorderAdvice.order; } /** * 前处理切面日志 * * @param pjp * 目标方法的返回结果 * @param logLevel * 日志级别 * @param targetMethod * 目标方法 * @param classMethodInfo * 目标类#方法 * @param isControllerMethod * 是否是controller类中的方法 */ private void preHandle(ProceedingJoinPoint pjp, RecordParameters.LogLevel logLevel, Method targetMethod, String classMethodInfo, boolean isControllerMethod) { StringBuilder sb = new StringBuilder(64); sb.append(prettyPlaceholder).append("[the way in]"); if (isControllerMethod) { sb.append(" request-path[").append(getRequestPath(targetMethod)).append("] "); } sb.append(classMethodInfo); Object[] parameterValues = pjp.getArgs(); if (parameterValues != null && parameterValues.length > 0) { String[] parameterNames = parameterNameDiscoverer.getParameterNames(targetMethod); if (parameterNames == null) { log.warn("Cannot determine parameter names. curr method -> {}", targetMethod.toGenericString()); parameterNames = new String[parameterValues.length]; for (int i = 0; i < parameterNames.length; i++) { parameterNames[i] = "UnknownParamName" + i; } } sb.append(", with parameters "); int iterationTimes = parameterValues.length; for (int i = 0; i < iterationTimes; i++) { Object parameterValue = parameterValues[i]; String prettyStr = routeIgnoreOrPretty(parameterValue); sb.append(prettyPlaceholder).append("\t").append(parameterNames[i]).append(" => ").append(prettyStr); if (i == iterationTimes - 1) { sb.append(prettyPlaceholder); } } } else { sb.append(", without any parameters"); } log(logLevel, sb.toString()); } /** * 后处理切面日志 * * @param logLevel * 日志级别 * @param targetMethod * 目标方法 * @param obj * 目标方法的返回结果 * @param classMethodInfo * 目标类#方法 * @param isControllerMethod * 是否是controller类中的方法 */ private void postHandle(RecordParameters.LogLevel logLevel, Method targetMethod, Object obj, String classMethodInfo, boolean isControllerMethod) { StringBuilder sb = new StringBuilder(64); sb.append(prettyPlaceholder).append("[the way out]"); if (isControllerMethod) { sb.append(" request-path[").append(getRequestPath(targetMethod)).append("] "); } sb.append(classMethodInfo); Class returnClass = targetMethod.getReturnType(); sb.append(prettyPlaceholder).append("\treturn type -> ").append(targetMethod.getReturnType()); if (!VOID_STR.equals(returnClass.getName())) { String prettyStr = routeIgnoreOrPretty(obj); sb.append(prettyPlaceholder).append("\treturn result -> ").append(prettyStr); } sb.append(prettyPlaceholder); log(logLevel, sb.toString()); } /** * 记录日志 * * @param logLevel * 要记录的日志的级别 * @param markerValue * formatter中占位符的值 */ private void log(RecordParameters.LogLevel logLevel, Object markerValue) { try { METHOD_MAP.get(logLevel.name()).invoke(log, "{}", markerValue); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("RecordParametersAdvice$AopSupport#log occur error!", e); } } /** * 将obj转换为 ignore字符串 或者 对应的格式化输出 * * @param obj * 需要转换的对象 * * @return ignore字符串 或者 对应的格式化输出 */ private String routeIgnoreOrPretty(Object obj) { String prettyStr; if (obj != null) { //noinspection rawtypes Class hitIgnoreTypeClass = ignoreParamTypesSet.stream().filter(x -> ClassUtils.isAssignable(x, obj.getClass())) .findFirst().orElse(null); if (hitIgnoreTypeClass != null) { prettyStr = ""; } else { prettyStr = doPretty(obj); } } else { prettyStr = doPretty(obj); } return prettyStr; } /** * 格式化输出 * * @param obj * 需要格式化的对象 * * @return 格式化后的字符串 */ String doPretty(Object obj) { String str; switch (handleMode) { case USE_JSON: try { str = JSON.toJSONString(obj); } catch (Throwable e) { log.warn("JSON.toJSONString(obj) occur exception, e.getMessage() -> {}", e.getMessage()); str = String.valueOf(obj); } break; case USE_TO_STRING: str = String.valueOf(obj); break; case CUSTOM_SERIALIZER: str = parameterSerializer.doSerializer(obj); break; default: throw new UnsupportedOperationException(); } return str; } /** * 获取请求path * * @param method * 当前方法 * @return 请求的path */ String getRequestPath(Method method) { try { return requestPathProvider == null ? "" : requestPathProvider.requestPath(method); } catch (Exception e) { log.warn("requestPathProvider.requestPath() occur exception, e -> {}", e.getMessage()); return ""; } } }