Spring boot 自定义注解+@Aspect实现切面输出日志,附源码下载!
内容简介
日志信息对于一个应用而言,无疑是至关重要的。访问记录,数据追踪,排查问题等操作,都会用到日志信息。一个优秀的日志信息包括了请求地址,入参,访问IP,出参等信息,在业务中写日志输出是相当烦锁的事情。本文介绍了利用注解+APO(@Aspect实现)的方案来实现日志的输出。使用时只需要在controller类的方法上加上一个注解即可。
实现步骤
添加引用
因为使用了切面,添加aop的依赖。出参以json的方式来输出,这里使用了google的gson。
<!-- aop 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- google json tool --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.6</version> </dependency>
自定义注解
添加一个自定义注解,这里以WebLog来命名。
/** * 自定义注解 * 功能:输出日志 */ @Retention(RetentionPolicy.RUNTIME) //何时使用该注解,此处为运行时 @Target({ElementType.METHOD}) //注解用于何处,此处为作用于方法上 @Documented //注解是否将包含在 JavaDoc 中 public @interface WebLog { /** * 属性 * 日志描述信息 * 默认为空字符串 * @return */ String description() default ""; }
配置切面及实现日志输出
面向切面编程时,常用的API拦截方式有Fliter,Interceptor,ControllerAdvice以及Aspect,它们的拦截顺序为 Fliter -> Interceptor -> ControllerAdvice -> Aspect -> controller。这里我们使用Aspect来实现。
Aspect可以拿得到方法响应中参数的值,但是拿不到原始的Http请求和相对应响应的方法,基于Controller层。对于统一异常处理和日志记录非常方便,有效地减少了代码的重复率。主要使用到的注解如下:
@Aspect:添加此注解的类,用来定义切面
@Pointcut:定义一个切点。
@Before: 在切点之前,织入相关代码;
@After: 在切点之后,织入相关代码;
@AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;
@AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;
@Around: 环绕,可以在切入点前后织入代码,并且可以自由的控制何时执行切点;
执行顺序:@Around -> @Before -> Controller Method -> @Around -> @After ->@AfterReturning
创建WebLogAspect类文件,添加@Aspect注解,在此类中定义切点,以及实现在切点前后的日志输出。
定义@Around,并在此方法中,执行切点
/** * 定义 @Around 环绕 * @param proceedingJoinPoint * @return * @throws Throwable */ @Around("webLog()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { logger.info("@Around"); long startTime = System.currentTimeMillis(); //调用接口的开始时间 // 执行切点,调用顺序:@Before -> 接口逻辑代码 -> @After -> @AfterReturning Object result = proceedingJoinPoint.proceed(); // 打印出参 logger.info("Response Args : {}", new Gson().toJson(result)); // 执行耗时 logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime); return result; }
切点前后输出日志信息
/** * 在切点之前织入 * @param joinPoint * @throws Throwable */ @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 开始打印请求日志 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 获取 @WebLog 注解的描述信息 String methodDescription = getAspectLogDescription(joinPoint); // 打印请求相关参数 logger.info("===================================== Start ====================================="); // 打印请求URL logger.info("URL : {}", request.getRequestURL().toString()); // 打印描述信息 logger.info("Description : {}", methodDescription); // 打印 Http method logger.info("HTTP Method : {}", request.getMethod()); // 打印调用 controller 的全路径及执行方法 logger.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); // 打印请求的IP logger.info("IP : {}", request.getRemoteAddr()); // 打印请求入参 logger.info("Request Args : {}", new Gson().toJson(joinPoint.getArgs())); } /** * 在切点之后织入 * @throws Throwable */ @After("webLog()") public void doAfter() throws Throwable { // 接口结束后换行,方便分割查看 logger.info("====================================== End ======================================" + LINE_SEPARATOR); } /** * 获取切面注解的描述 * @param joinPoint * @return * @throws Exception */ public String getAspectLogDescription(JoinPoint joinPoint) throws Exception { String targetName = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); Object[] arguments = joinPoint.getArgs(); Class targetClass = Class.forName(targetName); Method[] methods = targetClass.getMethods(); StringBuilder description = new StringBuilder(""); for (Method method : methods){ if (method.getName().equals(methodName)){ Class[] clazzs = method.getParameterTypes(); if (clazzs.length == arguments.length){ description.append(method.getAnnotation(WebLog.class).description()); break; } } } return description.toString(); }
使用
@RestController public class HelloController { @RequestMapping @WebLog(description = "欢迎信息接口") public String index(){ return "Welcome to training!"; } @GetMapping("/getUser/{id}") @WebLog(description = "获取用户信息接口") public UserInfo getUser(@PathVariable("id") Integer id) { //逻辑代码省略,直接返回 UserInfo userInfo = new UserInfo(); userInfo.setId(id); userInfo.setUserName("张小跑"); userInfo.setBirthday(new Date()); userInfo.setUserAge(22); userInfo.setIntroduction("应届毕业生"); return userInfo; } }
日志输出效果
源码下载
点击下载此文中的源码,文件不大,在打开的下载页面中,点击左侧的普通下载即可,如下图。
不能下载能留言。