使用@Aspect进行日志记录

1. 简介

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它通过将程序分解成不同的关注点来解决复杂性问题。在 AOP 中,一个关注点是一个横切关注点(cross-cutting concern),常见的关注点如日志记录、事务管理、安全性检查等。在传统的面向对象编程中,这些关注点往往会被分散到不同的类和方法中,导致代码重复、难以维护。AOP 提供了一种更好的方式来处理这些横切关注点,它通过将这些关注点从主要业务逻辑中分离出来,将其封装为可重用的模块,并在需要的时候将其动态地织入到主要业务逻辑中

AOP 的核心思想是切面(aspect),它是一个横切关注点的实现。切面定义了在何时、何处和如何进行横切关注点的实现。在 AOP 中,切面可以被视为一个横向的功能单元,它在系统中独立存在,与主要业务逻辑相互配合,实现系统的功能。

AOP 可以在很多场景下使用,比如日志记录、事务管理、安全性检查、性能监控、缓存管理等。AOP 已经成为许多企业级应用中不可或缺的一部分。常见的 AOP 框架包括 Spring AOP、AspectJ 等。

2. Spring AOP 和 AspectJ

Spring AOP 和 AspectJ 都是 AOP 的实现框架,但它们之间有一些区别:

  1. 实现方式:Spring AOP 是基于动态代理实现的,而 AspectJ 是基于编译时织入(CTW)和运行时织入(RTW)实现的。
  2. AOP 的能力:Spring AOP 只支持方法级别的连接点(join points)和切点(pointcuts),而 AspectJ 支持方法级别和更细粒度的连接点,比如字段访问、对象初始化等。AspectJ 还支持更多的切点表达式和切点选择器。
  3. 配置方式:Spring AOP 支持使用 XML 配置文件或注解来声明切面和切点,而 AspectJ 支持使用专门的 AspectJ 语言来声明切面和切点。
  4. 依赖关系:Spring AOP 是 Spring 框架的一部分,可以和 Spring 容器一起使用。而 AspectJ 是一个独立的框架,需要额外的配置和依赖库。

总的来说,Spring AOP 更适合简单的切面场景,比如事务管理、安全性检查、日志记录等,而 AspectJ 更适合复杂的切面场景,比如跨越多个对象、使用更细粒度的连接点、需要复杂的切点表达式等。

3. @Aspect实现切面常用注解

  1. @before: 前置通知,在方法执行之前执行。
  2. @After:后置通知,在方法执行后执行。
  3. @AfterReturning:返回通知,在方法返回结果之后执行。
  4. @AfterThrowing:异常通知,在方法抛出异常之后执行。
  5. @Around:环绕通知,围绕着方法执行。

4. 导入依赖包

spring-boot-starter-web包含了spring-boot-starter-aop,spring-boot-starter-aop包含了aspectjweaver,根据实际情况选择其中一个引入即可。

 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.5.4</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
 </dependency>

5. springboot实现

创建一个实体类

@Aspect
@Component
public class RequestLoggingAspect {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Around("@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController) "
            + "&& (@annotation(org.springframework.web.bind.annotation.RequestMapping) || @annotation(org.springframework.web.bind.annotation.PostMapping) || @annotation(org.springframework.web.bind.annotation.GetMapping))"
            + "&& within(com.example..*)")
    public Object logRequest(ProceedingJoinPoint joinPoint) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        logger.info("Request URL: {}", request.getRequestURL().toString());
        logger.info("HTTP Method: {}", request.getMethod());
        logger.info("IP Address: {}", request.getRemoteAddr());
        logger.info("Class Method: {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        logger.info("Request Parameters: {}", request.getParameterMap());

        Object result = null;
        try {
            result = joinPoint.proceed();
            logger.info("Response: {}", result);
        } catch (Exception e) {
            logger.error("Exception in {}.{}() with cause = '{}'", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), e.getCause() != null ? e.getCause() : "NULL");
            throw e;
        } finally {
            // 具体的日志记录实现
            addLog(joinPoint);
        }

        return result;
    }
}

选择@Around注解进行切入,是因为这种方式可以获悉方法执行时,是否抛出了异常,进行异常捕获和处理后,将异常再重新抛出,不会影响方法执行的主流程。

需要注意的是,这里仍然需要在 Spring Boot 应用程序启动类中加上 @EnableAspectJAutoProxy 注解来启用 AOP 支持。

@Aroundwithin(com.example..*)可以通过包路径配合正则表达式来指定切点过滤,@annotation将切面应用于所有被指定注解标记的方法上, @within(注解类型) 表达式来指定类上的注解进行过滤。

当前的切面类是具体的实现,不同的子模块可能有不同的需求,可以将当前实体类定义为抽象类,具体的实现由子模块定义service进行实现。同时也可以自定义注解LogOperate,使用自定义注解标识不需要记录日志的接口方法。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogOperate {
    boolean enable() default true;
}
    private void addLog(ProceedingJoinPoint joinPoint, OperateResult result) {
        try {
            Method method = getMethod(joinPoint);
            LogOperate logOperate = AnnotationUtils.getAnnotation(method, LogOperate.class);
            if (logOperate == null || logOperate.enable()) {
            }
        } catch (Exception e) {
            log.error("failed.", e);
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值