Aop日志实现

  1. AOP日志实现
    1. 注解方式实现AOP日志
    2. 自定义日志注解
    3. 配置切面类
      1. 环绕通知
      2. 配置切面
        1. 详细流程
      3. 案例

AOP日志实现

注解方式实现AOP日志

为什么要使用AOP?

  • 通常在项目中,我们需要配置日志打印来获取服务运行时我们需要的一些基本信息,这些信息是提供给我们后期用于排查错误的基本凭据

  • 但是如果直接在业务逻辑中添加日志打印的代码,会使得日志打印与业务逻辑产生耦合,也降低了业务代码的可读性,大量的日志打印可能都是一些重复的代码,降低了编程效率,更不便于统一管理

因此我们采用注解+AOP来配置需要被记录日志的方法

常见的AOP场景有:日志打印,权限认证,事务处理

自定义日志注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SystemLog {
    //业务信息,用于存放被增强方法的业务名称信息
    String businessName();
}

配置切面类

@Aspect 声明当前类是一个切面类

@Component 交由Spring容器管理

@Pointcut 配置切入点表达式

  • 这里我们使用注解的方式来配置切入点
    • @annotation(自定义注解全类名) 被自定义注解注释的方法都将成为连接点
@Component
@Aspect
public class LogAspect {

    @Pointcut("@annotation(com.os467.annotation.SystemLog)")
    public void pt(){

    }

    @Around("pt()")
    public Object printLog(ProceedingJoinPoint joinPoint){

    }

}

环绕通知

这里我们选择环绕通知,可以在此通知中为方法设置前置和后置通知,功能较为强大

需要提供一个参数ProceedingJoinPoint joinPoint

  • Proceedingjoinpoint 继承了 JoinPoint,是在JoinPoint的基础上暴露出proceed()这个方法,proceed很重要,这个是aop代理链执行的方法

joinPoint基本方法

//返回目标对象,即被代理的对象
Object getTarget();

//返回切入点的参数
Object[] getArgs();

//返回切入点的签名
Signature getSignature();

//返回切入的类型
String getKind();

配置切面

详细流程

配置切入点表达式

首先需要声明一个切入点表达式

使用**@Pointcut注解**

这里通过读取注解来获取到连接点

 @Pointcut("@annotation(com.os467.annotation.SystemLog)")
    public void pt(){

    }

配置环绕通知

@Around 指定配置切入点表达式的方法

ProceedingJoinPoint 连接点,即在底层动态代理能访问到的方法,调用proceed()来执行需要被增强的方法

  @Around("pt()")
    public Object printLog(ProceedingJoinPoint joinPoint){

    }

执行目标方法

  @Around("pt()")
    public Object printLog(ProceedingJoinPoint joinPoint){
        
        //前置通知...
        
        //执行目标方法
        ret = joinPoint.proceed();
        
        //后置通知...
        
        //返回方法执行后的结果
        return ret;

    }

通过joinPoint获取到切入点的方法签名

有时候我们还需要获取到自定义注解内的信息用于日志打印的凭据,因此我们在配置切面的时候还需要获取到方法的注解字节码对象

  • getSignature() 此方法会返回一个Signature实现类的对象,我们需要将其向下转型成MethodSignature

  • 通过getMethod() 获取到方法字节码,此时就可以调用getAnnotation()来获取到方法注解上的信息

//获取当前切入点的方法签名
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();

//获取到修饰方法的注解字节码对象
SystemLog systemLog = methodSignature.getMethod().getAnnotation(SystemLog.class);

案例

@Component
@Aspect
@Slf4j
public class LogAspect {

    @Pointcut("@annotation(com.os467.annotation.SystemLog)")
    public void pt(){

    }

    /**
     * 配置切面
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("pt()")
    public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {

        //获取目标方法的返回值
        Object ret;
        try {

            //前置通知
            printBefore(joinPoint);

            //执行目标方法
            ret = joinPoint.proceed();

            //后置通知
            printAfter(ret);

        } finally {
            // 结束后换行,获取到当前系统的换行符
            log.info("=======End=======" + System.lineSeparator());

        }

        //返回切入点方法执行结果
        return ret;

    }

    /**
     * 执行方法后需要打印的通知
     * @param ret
     */
    private void printAfter(Object ret) {
        // 打印出参
        log.info("Response       : {}",JSON.toJSONString(ret));
    }

    /**
     * 执行方法之前需要打印的通知
     */
    private void printBefore(ProceedingJoinPoint joinPoint) {

        //获取到当前线程的请求对象
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();

        HttpServletRequest request = null;

        //向下转型
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)attributes;

        //获取到当前线程请求对象
        request = requestAttributes.getRequest();

        //通过反射获取被增强方法上的注解对象
        SystemLog systemLog = getSystemLog(joinPoint);

        log.info("=======Start=======");
        // 打印请求 URL
        log.info("URL            : {}",request.getRequestURL());
        // 打印描述信息
        log.info("BusinessName   : {}",systemLog.businessName());
        // 打印 Http method
        log.info("HTTP Method    : {}", request.getMethod());
        // 打印调用 controller 的全路径以及执行方法
        log.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringType(),((MethodSignature)joinPoint.getSignature()).getName());
        // 打印请求的 IP
        log.info("IP             : {}",request.getRemoteHost());
        // 打印请求入参
        log.info("Request Args   : {}", JSON.toJSONString(joinPoint.getArgs()));

    }

    /**
     * 获取到被增强方法上的注解信息
     * @param joinPoint
     * @return
     */
    private SystemLog getSystemLog(ProceedingJoinPoint joinPoint) {

        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();

        SystemLog systemLog = methodSignature.getMethod().getAnnotation(SystemLog.class);

        return systemLog;

    }

}

使用注解实现日志通知

@SystemLog(businessName = "用户信息修改")
@PutMapping("/userInfo")
public ResponseResult updateUser(@RequestBody User user){
    return userService.updateUser(user);
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com

文章标题:Aop日志实现

字数:1.2k

本文作者:Os467

发布时间:2022-10-01, 23:47:22

最后更新:2022-10-01, 23:47:50

原始链接:https://os467.github.io/2022/10/01/Aop%E6%97%A5%E5%BF%97%E5%AE%9E%E7%8E%B0/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

×

喜欢就点赞,疼爱就打赏