一开始的时候关于这个切面的定义非常简单:拦截所有 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 因为方法的元数据都可以被它们共享。
评论