需求:要求接口被调用时要打印被调用方法名,以及入参情况,参数格式化时选择fastjson
注:使用fastjson序列化时脱敏,建议入参统一使用自定义的对象类型作为入参
如果不需要参数脱敏,直接使用增强中相关代码,并去除参数脱敏相关代码即可
- 新建注解,用于实现参数打印功能的增强
该增强可以加在类上,方法上
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ParamInfo {
/**
* 取消统一打印参数
* 默认为false统一打印
* 如需自定义参数打印 请赋值为true
*/
boolean unPrint() default false;
/**
* 需要脱敏的字段,如密码等
*/
String[] ignoreFields() default {};
}
- 自定义序列化规则
基于fastjson实现,可以根据自己的业务具体实现
/**
* 序列化过滤器:值替换
*
*/
public class ReplaceFieldFilter implements ValueFilter {
/**
* 需要进行替换的属性名和替换值
* key:属性名
* value:替换值
*/
private Map<String, Object> fieldMap;
public ReplaceFieldFilter() {
}
public ReplaceFieldFilter(Map<String, Object> fieldMap) {
this.fieldMap = fieldMap;
}
/**
* @param o 被格式化的对象本身
* @param name 本次处理的属性名
* @param value 本次处理的属性值
* @return
*/
@Override
public Object process(Object o, String name, Object value) {
if(!CollectionUtils.isEmpty(fieldMap)){
Iterator<Map.Entry<String, Object>> iterator = fieldMap.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry<String, Object> next = iterator.next();
if(next.getKey().equalsIgnoreCase(name)){
return next.getValue();
}
}
}
return value;
}
public Map<String, Object> getFieldMap() {
return fieldMap;
}
public void setFieldMap(Map<String, Object> fieldMap) {
this.fieldMap = fieldMap;
}
/**
* 传入需要脱敏的字段名,序列化时格式化为 * 号
*/
public ReplaceFieldFilter(String... fields) {
String str = "******";
fieldMap = new HashMap<>(4);
for (String field : fields) {
fieldMap.put(field, str);
}
}
}
- 写参数打印增强,这里选择环绕增强
方便打印出入参,还有可以自定义异常的返回值
@Component
@Aspect
//表示增强的执行顺序,如果多个增强,数值小的先被执行
@Order(0)
public class ParamInfoAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(ParamInfoAspect.class);
private static final ReplaceFieldFilter DEFAULT_FIELD_FILTER = new ReplaceFieldFilter("password");
/**
* 方法自定义脱敏规则
* key:方法名,value:具体的规则
*/
private static final Map<String, ReplaceFieldFilter> OTHER_FIELD_FILTER = new HashMap<>();
@Value("${response.timeout:10000}")
private long timeoutMs;
@Around("@within(com.annotations.ParamInfo) || @annotation(com.annotations.ParamInfo)")
public Object printParam(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = null;
String methodName = null;
boolean notAlarm = false;
try {
Object[] args = joinPoint.getArgs();
// 获取方法对象与注解
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
methodName = method.getName();
ParamInfo annotation = JoinPointUtils.getAnnotation(method, ParamInfo.class, false);
// 超时无需告警
notAlarm = annotation.notAlarm();
//如果无需打印参数,直接跳过,IP地址可根据自己实际场景定义实际的获取规则
if (annotation.unPrint()) {
result = joinPoint.proceed();
LOGGER.info("clientIp:[{}], method:[{}]", RpcContext.getContext().getRemoteHost(), methodName);
return result;
}
// 如果存在脱敏字段,则获取自定义规则,如果未获取到则新增规则
ReplaceFieldFilter filter = DEFAULT_FIELD_FILTER;
String[] fields = annotation.ignoreFields();
if (fields.length > 0) {
filter = OTHER_FIELD_FILTER.computeIfAbsent(methodName, (k) -> new ReplaceFieldFilter(fields));
}
//参数整合,多字段入参整合为对象,单个对象入参格式不变
Object param;
if (args.length == 1) {
param = args[0];
}
else if (args.length == 0) {
param = new LinkedHashMap<>();
}
else {
Map<String, Object> paramMap = new LinkedHashMap<>();
String[] parameterNames = signature.getParameterNames();
for (int i = 0; i < parameterNames.length; i++) {
paramMap.put(parameterNames[i], args[i]);
}
param = paramMap;
}
LOGGER.info("clientIp:[{}], method:[{}], parameter:[{}]", RpcContext.getContext().getRemoteHost(), methodName, JSON.toJSONString(param, filter));
result = joinPoint.proceed();
return result;
} catch (Throwable t) {
LOGGER.error("system is error:", t);
//可在这里定义程序异常时的错误返回值
result = JoinPointUtils.getRightReturn(joinPoint, ErrorCode.SYSTEM_ERROR);
return result;
} finally {
long cost = System.currentTimeMillis() - startTime;
LOGGER.info("result:[{}]", result);
LOGGER.info("request cost:{}ms", cost);
// 超时触发告警
if (!notAlarm && cost > timeoutMs) {
LOGGER.error("interface response time exceeds {}ms, please check it! methodName:[{}]", timeoutMs, methodName);
}
}
}
}
4.切点工具类
用于增强中的一些常用操作的封装
/**
* AOP切点工具类
*
* @author zhangYiCong
* @date 2022-04-15 13:33:23
*/
public class JoinPointUtils {
/**
* 从切点获取方法对象
*
* @param joinPoint
* @return
*/
public static Method getMethod(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
return signature.getMethod();
}
/**
* 从切点获取指定注解
*
* @param joinPoint
* @param annotationClass
* @param <T>
* @return
*/
public static <T extends Annotation> T getAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass) {
return getAnnotation(joinPoint, annotationClass, true);
}
/**
* 从切点获取指定注解
*
* @param joinPoint
* @param annotationClass
* @param alwaysOnMethod 如果为false,方法上获取不到时,会从类上获取
* @param <T>
* @return
*/
public static <T extends Annotation> T getAnnotation(ProceedingJoinPoint joinPoint, Class<T> annotationClass, boolean alwaysOnMethod) {
Method method = getMethod(joinPoint);
return getAnnotation(method, annotationClass, alwaysOnMethod);
}
/**
* 从方法上获取指定注解
*
* @param method
* @param annotationClass
* @param alwaysOnMethod 如果为false,方法上获取不到时,会从类上获取
* @param <T>
* @return
*/
public static <T extends Annotation> T getAnnotation(Method method, Class<T> annotationClass, boolean alwaysOnMethod) {
//获取方法是否被注解标记
T annotation = method.getAnnotation(annotationClass);
//如果在方法上已经获取到注解或者指定只从方法找,直接返回
return annotation != null || alwaysOnMethod ? annotation : method.getDeclaringClass().getAnnotation(annotationClass);
}
/**
* 传入错误码和响应,封装后返回实际的响应对象
*
* @param joinPoint
* @param code
* @param message
* @return
*/
public static Object getRightReturn(ProceedingJoinPoint joinPoint, int code, String message) {
// 获取方法的签名对象
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
Class<?> returnType = method.getReturnType();
JSONObject returnResult = new JSONObject();
returnResult.put("code", code);
returnResult.put("message", message);
return returnResult.toJavaObject(returnType);
}
}