SpringBootAOP自定义注解记录项目业务日志

1,212 阅读3分钟

这是我参与11月更文挑战的第6天,活动详情查看:[2021最后一次更文挑战]

先引入aop依赖

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.5.2</version>
</dependency>

关于AOP 建议读者好好学习理解哈,这里不介绍了,就一句话:很重要很重要!!!

创建编写相关代码

自定义注解

/**
* 自定义 log注解
*
* @author Smile
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
  /**
   * 记录业务名称
   *
   * @return 业务名称
   */
  String value() default "";
}

我这边建立的是方法层级的

切面类

@Resource
private SysLogService logService;
private SysLogPO sysLogPO = new SysLogPO();

/**
 * 这个是切点的意思,从什么地方切入: 可以是具体的类或者注解 ,也可以是模糊的类路径 * *..*Api.*(..)
 */
@Pointcut("@annotation(com.smile.ssm.aop.annotation.SysLog)")
public void annotationPointcut() {
}

/**
 * 前置通知, 在方法执行之前执行
 *
 * @param joinPoint xx
 */
@Before("annotationPointcut()")
public void beforePointcut(JoinPoint joinPoint) {
    System.out.println("beforePointcut");

}

/**
 * 环绕通知, 围绕着方法执行
 *
 * @param point
 * @return
 * @throws Throwable
 */
@Around("annotationPointcut()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {
    System.out.println("doAround");
    RequestAttributes ra = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes sra = (ServletRequestAttributes) ra;
    HttpServletRequest request = sra.getRequest();
    //这个是获取调用的方法类型:get post ...
    String method1 = request.getMethod();
    String url = request.getRequestURL().toString();
    sysLogPO.setMethod(url);
    sysLogPO.setDeleted(false);
    sysLogPO.setCreateTime(LocalDateTime.now());
    //获得执行方法的类名
    String targetName = point.getTarget().getClass().getName();
    //获得执行方法的方法名
    String methodName = point.getSignature().getName();
    //获取切点方法的所有参数类型
    Object[] arguments = point.getArgs();
    try {
        Class targetClass = Class.forName(targetName);
        //获取公共方法,不包括类私有的
        Method[] methods = targetClass.getMethods();
        String value = "";
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                //对比方法中参数的个数
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    value = method.getAnnotation(SysLog.class).value();
                    break;
                }
            }
        }
        sysLogPO.setBusinessName(value);
    } catch (Exception e) {
        e.printStackTrace();
    }

    logService.save(sysLogPO);
    log.info("===请求开始, 各个参数, url: " + url + ", method: " + method1 + ", uri:" + request.getRequestURI() + ", params:" + request.getQueryString());
    if (arguments != null) {
        for (Object argument : arguments) {
            log.info("===接口入参:" + JSON.toJSONString(argument));
        }
    }
    return point.proceed();

}

/**
 * 返回通知, 在方法返回结果之后执行
 *
 * @param joinPoint xx
 */
@AfterReturning("annotationPointcut()")
public void doAfterReturning(JoinPoint joinPoint) {
    System.out.println("doAfterReturning");
}

/**
 * 异常通知, 在方法抛出异常之后
 */
@AfterThrowing("annotationPointcut()")
public void doAfterThrowing(JoinPoint joinPoint) {
    System.out.println("doAfterThrowing");
}

/**
 * 后置通知, 在方法执行之后执行
 */
@After("annotationPointcut()")
public void doAfter(JoinPoint joinPoint) {
    System.out.println("doAfter");
}
@Pointcut

1execution(* *(..))  
//表示匹配所有方法  
2execution(public * com.smile.ssm.service.*Api*(..))  
//表示匹配com.smile.ssm.UserService中所有的公有方法  
3execution(* com.smile.ssm..*.*(..))  
//表示匹配com.savage.server包及其子包下的所有方法 
/**
 * 搭配切点使用前置通知, 在方法执行之前执行
 *
 * @param joinPoint xx
 */
@Before("annotationPointcut()")
/**
 * 环绕通知, 围绕着方法执行
 *
 * @param point
 * @return
 * @throws Throwable
 */
@Around("annotationPointcut()")
/**
 * 返回通知, 在方法返回结果之后执行
 *
 * @param joinPoint xx
 */
/**
 * 异常通知, 在方法抛出异常之后
 */
/**
 * 后置通知, 在方法执行之后执行
 */
关于几个增强注解的执行循序
正常情况: doAround->beforePointcut->doAfterReturning->doAfter

image.png

异常情况: doAround->beforePointcut->doAfterThrowing->doAfter

1638233844(1).jpg

特殊情况:

after 里面出现错误的话将 AfterThrowing捕获不到 它只对于被切入的方法所以这里需要注意避免出现多条日志的情况哦。

上面切面类测试代码中写了挺多东西的就是为了测试过程以及搭配:

日志可选择:

  1. @Before + @After
  2. @Around

上面两种随便搭配就好!!!

Test

效果:调用业务的日志存库了,并且系统能够打印调用日志!!! image.png

image.png

image.png

END

完美完成aop自定义业务注解啦,下一篇:项目引入服务器端logback日志管理。

源码地址:gitee.com/smile_lx/ss…

今天到洗牙,哇好难受呀,一嘴血味>!-!<