com.ideaaedi.commonspring.aop.ParameterRecorderAdvice Maven / Gradle / Ivy
package com.ideaaedi.commonspring.aop;
import com.alibaba.fastjson2.JSON;
import com.ideaaedi.commonspring.lite.params.ParameterHandleModeEnum;
import com.ideaaedi.commonspring.lite.params.ParameterRecorderCustomizer;
import com.ideaaedi.commonspring.lite.params.ParameterSerializer;
import com.ideaaedi.commonspring.lite.params.RecordParameters;
import com.ideaaedi.commonspring.lite.params.RequestEntranceJudger;
import com.ideaaedi.commonspring.lite.params.RequestPathProvider;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
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
*/
public class ParameterRecorderAdvice implements MethodInterceptor, InitializingBean, Ordered {
public static final String BEAN_NAME = "parameterRecorderAdvice";
private static final Logger log = LoggerFactory.getLogger(ParameterRecorderAdvice.class);
/**
* 无返回值
*/
private static final String VOID_STR = void.class.getName();
private static final Class> LOG_CLASS = log.getClass();
private static final Map METHOD_MAP = new ConcurrentHashMap<>(8);
private ParameterSerializer parameterSerializer;
private RequestPathProvider requestPathProvider;
private ParameterNameDiscoverer parameterNameDiscoverer;
private RequestEntranceJudger requestEntranceJudger;
/**
* 需要包含的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;
/**
* 定制器
*/
private final ParameterRecorderCustomizer parameterRecorderCustomizer;
public ParameterRecorderAdvice(Set includePrefixes, Set excludePrefixes,
ParameterHandleModeEnum handleMode,
boolean pretty, @SuppressWarnings("rawtypes") Class[] ignoreParamTypes,
ParameterRecorderCustomizer parameterRecorderCustomizer) {
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));
this.parameterRecorderCustomizer = Objects.requireNonNull(parameterRecorderCustomizer, "parameterRecorderCustomizer cannot be null.");
}
public void afterPropertiesSet() throws Exception {
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);
// 定制相关工具
parameterNameDiscoverer = parameterRecorderCustomizer.customParameterNameDiscoverer();
requestPathProvider = parameterRecorderCustomizer.customRequestPathProvider();
parameterSerializer = parameterRecorderCustomizer.customParameterSerializer();
requestEntranceJudger = parameterRecorderCustomizer.customRequestEntranceJudger();
if (handleMode == ParameterHandleModeEnum.CUSTOM_SERIALIZER) {
Objects.requireNonNull(parameterSerializer, "parameterSerializer should not be null while "
+ "parameterHandleMode is set to 'CUSTOM_SERIALIZER'.");
}
}
/**
* 环绕增强
*/
@Nullable
@Override
public Object invoke(@Nonnull MethodInvocation methodInvocation) throws Throwable {
// 获取目标method
Method targetMethod = methodInvocation.getMethod();
// 获取目标Class
Class> targetClazz = targetMethod.getDeclaringClass();
String clazzName = targetClazz.getCanonicalName();
String methodReference = clazzName + "#" + targetMethod;
boolean anyMatchExclude = excludePrefixes.stream().anyMatch(methodReference::startsWith);
if (anyMatchExclude) {
log.debug("anyMatchExclude is true, skip ParameterRecorderAdvice. methodReference is -> {}",
methodReference);
return methodInvocation.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 methodInvocation.proceed();
}
}
// 获取目标annotation
boolean useDefaultAnnoValue = false;
RecordParameters annotation = targetMethod.getAnnotation(RecordParameters.class);
if (annotation == null) {
annotation = targetClazz.getAnnotation(RecordParameters.class);
// 如果是通过execution触发的,那么annotation可能为null, 那么给其赋予默认值即可
if (annotation == null) {
annotation = (RecordParameters) AnnotationUtils.getDefaultValue(RecordParameters.class);
useDefaultAnnoValue = true;
}
}
// 是否需要记录入参、出参
boolean shouldRecordInputParams;
boolean shouldRecordOutputParams;
RecordParameters.LogLevel logLevel;
if (useDefaultAnnoValue) {
shouldRecordInputParams = shouldRecordOutputParams = true;
logLevel = RecordParameters.LogLevel.INFO;
} else {
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();
}
final String classMethodInfo = "Class#Method -> " + clazzName + "#" + targetMethod.getName();
boolean isEntryMethod = requestEntranceJudger.ifEntrance(targetClazz, targetMethod, methodInvocation.getArguments());
if (shouldRecordInputParams) {
preHandle(methodInvocation, logLevel, targetMethod, classMethodInfo, isEntryMethod);
}
Object obj = methodInvocation.proceed();
if (shouldRecordOutputParams) {
postHandle(logLevel, targetMethod, obj, classMethodInfo, isEntryMethod);
}
return obj;
}
@Override
public int getOrder() {
return parameterRecorderCustomizer.customAdviceOrder();
}
/**
* 前处理切面日志
*
* @param methodInvocation 方法调用信息
* @param logLevel 日志级别
* @param targetMethod 目标方法
* @param classMethodInfo 目标类#方法
* @param isEntryMethod 是否是请求入口类中的方法
*/
private void preHandle(MethodInvocation methodInvocation, RecordParameters.LogLevel logLevel,
Method targetMethod, String classMethodInfo, boolean isEntryMethod) {
StringBuilder sb = new StringBuilder(64);
sb.append(prettyPlaceholder).append("[the way in]");
if (isEntryMethod) {
sb.append(" request-path[").append(getRequestPath(targetMethod)).append("] ");
}
sb.append(classMethodInfo);
Object[] parameterValues = methodInvocation.getArguments();
if (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 isEntryMethod 是否是请求入口类中的方法
*/
private void postHandle(RecordParameters.LogLevel logLevel, Method targetMethod,
Object obj, String classMethodInfo, boolean isEntryMethod) {
StringBuilder sb = new StringBuilder(64);
sb.append(prettyPlaceholder).append("[the way out]");
if (isEntryMethod) {
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 = null;
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);
}
}
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);
}
}
case USE_TO_STRING -> str = String.valueOf(obj);
case CUSTOM_SERIALIZER -> str = parameterSerializer.doSerializer(obj);
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 "";
}
}
}