Aspect实现对方法日志的拦截记录
在实际的业务系统中,我们通常都希望程序自动的打印方法的入参和返回值,某些特定的方法可能不想打印返回值(返回数据过大,打印日志影响效率),特有了下面的实现。
1、忽略返回值的java注解类
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 不需要打印返回值的log * @author yangzhilong * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface NotPrintResponseLog { public boolean value() default false; }
2、日志记录切面类
1 import java.io.Serializable; 2 import java.lang.reflect.Method; 3 import java.util.Arrays; 4 import java.util.List; 5 import java.util.concurrent.atomic.AtomicInteger; 6 7 import org.aspectj.lang.ProceedingJoinPoint; 8 import org.aspectj.lang.annotation.Around; 9 import org.aspectj.lang.annotation.Aspect; 10 import org.aspectj.lang.annotation.Pointcut; 11 import org.aspectj.lang.reflect.MethodSignature; 12 import org.springframework.stereotype.Component; 13 import org.springframework.web.multipart.MultipartFile; 14 15 import com.alibaba.fastjson.JSONObject; 16 17 18 import lombok.extern.slf4j.Slf4j; 19 20 21 /** 22 * 记录Rest和service的方法的入参和返回值
* @author yangzhilong 23 */ 24 @Aspect 25 @Component 26 @Slf4j 27 public class CommonLogAspect { 28 @Pointcut(value = "execution(public * com.tomato..*Impl.*(..))") 29 private void pointcutService() { 30 31 } 32 @Pointcut(value = "execution(public * com.tomato..*Rest*.*(..))") 33 private void pointcutRest() { 34 35 } 36 /* 37 //拦截restmapping注解 38 @Pointcut(value = "execution(@org.springframework.web.bind.annotation.RequestMapping public * com.tomato..*(..))") 39 private void pointCut() { 40 41 } 42 43 //拦截post注解 44 @Pointcut(value = "execution(@org.springframework.web.bind.annotation.PostMapping public * com.tomato..*(..))") 45 private void pointCutPost() { 46 47 } 48 49 //拦截get注解 50 @Pointcut(value = "execution(@org.springframework.web.bind.annotation.GetMapping public * *com.tomato..*(..))") 51 private void pointCutGet() { 52 53 } 54 55 //拦截Delete注解 56 @Pointcut(value = "execution(@org.springframework.web.bind.annotation.DeleteMapping public * com.tomato..*(..))") 57 private void pointCutDelete() { 58 59 } 60 61 //拦截put注解 62 @Pointcut(value = "execution(@org.springframework.web.bind.annotation.PutMapping public * com.tomato..*(..))") 63 private void pointCutPut() { 64 65 }*/ 66 67 @Pointcut("pointcutService()|| pointcutRest()") 68 private void pointcut() { 69 } 70 71 @Around(value = "pointcut()") 72 public Object Around(ProceedingJoinPoint pjp) throws Throwable { 73 String classAndMethodName = null; 74 Method currentMethod = null; 75 76 try { 77 currentMethod = this.getCurrentMethod(pjp); 78 classAndMethodName = this.getCurrentCompleteMethodName(pjp); 79 } catch (Throwable e) { 80 log.error("初始化日志记录信息时出错", e); 81 return pjp.proceed(); 82 } 83 84 // 处理入参 85 this.processBefore(pjp, classAndMethodName); 86 87 Object result = null; 88 try { 89 // 调用目标方法 90 result = pjp.proceed(); 91 } catch (Throwable e) { 92 // 目标方法异常了 93 log.info("end执行方法:{}发生异常,异常简述:{}", classAndMethodName, e.getMessage()); 94 throw e; 95 } 96 97 // 处理返回值 98 processReturnValue(result, currentMethod, classAndMethodName); 99 100 return result; 101 } 102 103 /** 104 * 得到当前方法的对象引用 105 * @param pjp 106 * @return 107 * @throws NoSuchMethodException 108 * @throws SecurityException 109 */ 110 private Method getCurrentMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException, SecurityException { 111 MethodSignature mig = (MethodSignature) pjp.getSignature(); 112 return pjp.getTarget().getClass().getMethod(mig.getName(), mig.getParameterTypes()); 113 } 114 115 /** 116 * 得到完整的方法名 117 * @param pjp 118 * @return 119 */ 120 private String getCurrentCompleteMethodName(ProceedingJoinPoint pjp) { 121 return pjp.getTarget().getClass() + "的" + pjp.getSignature().getName() + "方法"; 122 } 123 124 /** 125 * 处理入参打印 126 * @param pjp 127 * @param classAndMethodName 128 */ 129 private void processBefore(ProceedingJoinPoint pjp, String classAndMethodName) { 130 try { 131 if(null==pjp.getArgs() || pjp.getArgs().length==0) { 132 log.info("begin执行方法:{},方法无入参", classAndMethodName); 133 } else { 134 if(pjp.getArgs().length == 1) { 135 if (pjp.getArgs()[0] instanceof Serializable) { 136 if(isFile(pjp.getArgs()[0])) { 137 log.info("begin执行方法:{},入参为文件类型,文件名为:{}", classAndMethodName, getFileName(pjp.getArgs()[0])); 138 } else { 139 log.info("begin执行方法:{},入参为:{}", classAndMethodName, JSONObject.toJSONString(pjp.getArgs()[0])); 140 } 141 } 142 } else { 143 log.info("begin执行方法:{},有多个入参", classAndMethodName); 144 List<Object> list = Arrays.asList(pjp.getArgs()); 145 final AtomicInteger index = new AtomicInteger(1); 146 list.stream().filter(x -> x instanceof Serializable).forEach(x -> { 147 if(isFile(x)) { 148 log.info("入参{}:{}", index.get(), getFileName(x)); 149 } else { 150 log.info("入参{}:{}", index.get(), JSONObject.toJSONString(x)); 151 } 152 index.incrementAndGet(); 153 }); 154 } 155 } 156 } catch (Throwable e) { 157 log.error("记录入参日志的时候出错:", e); 158 } 159 } 160 161 /** 162 * 处理返回值 163 * @param result 164 * @param currentMethod 165 * @param classAndMethodName 166 */ 167 private void processReturnValue(Object result, Method currentMethod, String classAndMethodName) { 168 if(null == result) { 169 return; 170 } 171 try { 172 if(!currentMethod.isAnnotationPresent(NotPrintResponseLog.class) && result instanceof Serializable) { 173 log.info("end执行方法:{},返回结果:{}", classAndMethodName, JSONObject.toJSONString(result)); 174 } 175 } catch (Throwable e) { 176 log.error("记录返回日志的时候出错:", e); 177 } 178 179 } 180 181 /** 182 * 获取文件上传的文件名 183 * @param file 184 * @return 185 */ 186 private String getFileName(Object file) { 187 return null==file ? "空文件" : ((MultipartFile)file).getName(); 188 } 189 190 /** 191 * 判断是否是文件类型 192 * @param obj 193 * @return 194 */ 195 private boolean isFile(Object obj) { 196 return obj instanceof MultipartFile; 197 } 198 }
20180530补充:
在aop的逻辑内,先走@Around注解的方法。然后是@Before注解的方法,然后这两个都通过了,走核心代码,核心代码走完,无论核心有没有返回值,都会走@After方法。然后如果程序无异常,正常返回就走@AfterReturn,有异常就走@AfterThrowing。