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.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 "";
}
}
}