一开始的时候关于这个切面的定义非常简单:拦截所有 Controller 层调用,打印输入参数和返回值。around 方法可以这么写:
@Around("writeLog()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) {
String methName= joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
StringBuilder sb = new StringBuilder();
//拼接日志相关代码 ...
log.info(sb.toString());
try {
final Object proceed = joinPoint.proceed();
final String s = JSONObject.toJSONString(joinPoint.proceed());
log.info("方法路径: {} 返回值: {}", methName, s);
return proceed;
} catch (Throwable e) { e.printStackTrace(); }
return Result.error();
}
复制代码
这么做有一个缺陷就是不能区分对待,没法指定哪个方法可以不打印日志,为了使之使用起来比较灵活,需要引入一个注解类,有了这个注解信息,在 around 方法中,去解析方法上的注解信息,来判断是否要打印日志,注解类定义如下:
public @interface ControllerLayerLogPrintControl {
/* 入参日志打印控制 默认为true */
boolean input() default true;
/** 出参日志打印控制 默认为true */
boolean output() default true;
}
复制代码
当然,如果需要的话,还可以指定日志的打印级别。有了这个注解类,再重构一下 around 方法
@Around("writeLog()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
ControllerLayerLogPrintControl logPrintControl = methodSignature.getMethod().getAnnotation(ControllerLayerLogPrintControl.class);
String methName= methodSignature.getDeclaringTypeName() + "." + methodSignature.getName();
Object[] args = joinPoint.getArgs();//获取参数值
String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();//参数名
if (logPrintControl == null || logPrintControl.input()) { //控制是否要打印入参
StringBuilder sb = new StringBuilder();
//拼接日志相关代码
log.info(sb.toString());
}
try {
final Object proceed = joinPoint.proceed();
if (logPrintControl == null || logPrintControl.output()) { //控制是否要打印出参
String resultJson = JSONObject.toJSONString(proceed);
log.info("方法路径: {} 返回值: {}", methName, resultJson);
}
return proceed;
} catch (Throwable e) {
e.printStackTrace();
}
return Result.error();
}
复制代码
实现起来特别简单,有个不好的地方就是代码看起来比较凌乱。接下来,继续重构,让它看起来更美好一点。在这里引入闭包,大体思路是将打印入参的代码和打印出参的代码封装到闭包中(其实就是一个内部类的两个方法)。
首先定义一个日志打印器接口 LogPrinter
public interface LogPrinter {
/**打印请求入参*/
void printInput();
/**
* 打印请求返回值
* @param result 从controller中拿到的返回值
*/
void printOutput(Object result);
}
复制代码
接下来在切面类中新增一个方法:
private LogPrinter getLogPrinter(final ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
ControllerLayerLogPrintControl logPrintControl = methodSignature.getMethod().getAnnotation(ControllerLayerLogPrintControl.class);
String methName= methodSignature.getDeclaringTypeName() + "." + methodSignature.getName();
Object[] args = joinPoint.getArgs();//获取参数值
String[] parameterNames = methodSignature.getParameterNames();//参数名
return new LogPrinter() {
@Override
public void printInput() {
if (logPrintControl == null || logPrintControl.input()) {
StringBuilder sb = new StringBuilder();
//拼接日志相关代码
log.info(sb.toString());
}
}
@Override
public void printOutput(Object result) {
if (logPrintControl == null || logPrintControl.output()) {
log.info("方法路径: {} 返回值: {}", methName, JSONObject.toJSONString(result));
}
}
};
}
复制代码
最后,再改造一下 around 方法
@Around("writeLog()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) {
LogPrinter logPrinter = getLogPrinter(joinPoint);
logPrinter.printInput();
try {
final Object proceed = joinPoint.proceed();
logPrinter.printOutput(proceed);
return proceed;
} catch (Throwable e) {e.printStackTrace();}
return Result.error();
}
复制代码
可以看出 aroundMethod
方法确实简洁美观了不少。 getLogPrinter
方法接收JoinPoint
作为参数,最后返回一个内部类,该内部类其实就是闭包的一种体现,闭包中维持了它所属作用域的所有变量,printInput
调完后,可以直接调用 printOutput
因为方法的元数据都可以被它们共享。
评论