【学习总结|DAY033】后端Web进阶(AOP)

在当今的软件开发领域,提高代码的可维护性、可扩展性以及减少重复代码是至关重要的。Spring AOP(Aspect Oriented Programming,面向切面编程)作为一种强大的编程思想和技术,在解决这些问题上发挥着重要作用。本文将结合实际代码示例,深入探讨 Spring AOP 的相关知识,帮助大家更好地掌握这一技术。

一、AOP 基础概念

1.1 什么是 AOP

AOP 即面向切面编程,它可以简单理解为面向特定方法编程。在实际开发中,当部分业务方法运行较慢,需要定位执行耗时较长的接口时,就可以利用 AOP 来统计每一个业务方法的执行耗时。比如在一个部门管理系统中,有获取部门列表、删除部门、根据 ID 获取部门等业务方法,若要统计这些方法的执行耗时,使用 AOP 能高效地实现这一需求。

1.2 AOP 核心概念

  • 连接点(JoinPoint):可以被 AOP 控制的方法,暗含方法执行时的相关信息,例如目标类名、方法名、方法参数等。
  • 通知(Advice):那些重复的逻辑,也就是共性功能,最终体现为一个方法。如统计方法执行耗时的逻辑就是一个通知。
  • 切入点(PointCut):匹配连接点的条件,通知仅会在切入点方法执行时被应用。通过切入点表达式来定义,用于决定哪些方法需要加入通知。
  • 切面(Aspect):描述通知与切入点的对应关系,由通知和切入点组成。
  • 目标对象(Target):通知所应用的对象。

二、Spring AOP 快速入门

2.1 需求分析

以统计所有部门管理业务层方法的执行耗时为例,在未使用 AOP 时,需要在每个业务方法中重复编写获取开始时间、运行原始方法、获取结束时间并计算耗时的代码,这样会导致大量重复代码。

2.2 实现步骤

  • 导入依赖:在pom.xml文件中引入 Spring AOP 的依赖。

    org.springframework.boot
    spring-boot-starter-aop

  • 编写 AOP 程序:创建一个切面类,使用@Aspect@Component注解,在方法上使用@Around等通知注解,并编写切入点表达式。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class RecordTimeAspect {
    @Around("execution(* com.itheima.service.impl.*.*(..))")
    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
        long beginTime = System.currentTimeMillis();
        Object result = pjp.proceed();
        long endTime = System.currentTimeMillis();
        log.info("执行耗时: {} ms", endTime - beginTime);
        return result;
    }
}

在上述代码中,@Around环绕通知会在目标方法执行前后都执行。通过ProceedingJoinPointproceed方法调用原始方法,获取方法的返回值,并计算执行耗时进行日志记录。

三、AOP 进阶

3.1 通知类型

根据通知方法执行时机的不同,常见的通知类型有以下五类:

  • @Around(环绕通知):在目标方法前、后都被执行,需要手动调用ProceedingJoinPoint.proceed()来执行原始方法,方法返回值必须指定为Object来接收原始方法的返回值。
  • @Before(前置通知):在目标方法前被执行。
  • @After(后置通知):在目标方法后被执行,无论是否有异常都会执行。
  • @AfterReturning(返回后通知):在目标方法后被执行,有异常不会执行。
  • @AfterThrowing(异常后通知):发生异常后执行。

3.2 切入点表达式

  • execution:根据方法的签名来匹配,语法为execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws异常?),其中带?的部分可以省略。可以使用通配符*(单个独立的任意符号)和..(多个连续的任意符号),还可以使用&&||!来组合复杂的切入点表达式。例如:
@Before("execution(public void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
public void before(JoinPoint joinPoint) {}

  • @annotation:用于匹配标识有特定注解的方法。例如:
@Around("@annotation(com.itheima.anno.Logoperation)")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {}

3.3 通知顺序

当有多个切面的切入点都匹配到目标方法时,默认按照切面类的类名字母排序执行通知方法。也可以使用@Order(数字)加在切面类上来控制顺序,数字小的先执行目标方法前的通知,数字小的后执行目标方法后的通知。

四、AOP 案例实战

4.1 案例需求

将系统中增、删、改相关接口的操作日志记录到数据库表中,日志信息包含操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长。

4.2 实现思路

  • 选择通知类型:采用@Around环绕通知,因为它可以在方法执行前后获取相关信息,方便记录日志。
  • 编写切入点表达式:由于增、删、改方法名定义规范,分别为savedeleteupdate,切入点表达式可以写成:
@Around("execution(* com.itheima.controller.*.save(..)) || execution(* com.itheima.controller.*.delete(..)) || execution(* com.itheima.controller.*.update(..))")

  • 获取连接点信息:在环绕通知方法中,通过ProceedingJoinPoint获取目标类名、方法名、方法参数等信息。
@Around("execution(* com.itheima.controller.*.save(..)) || execution(* com.itheima.controller.*.delete(..)) || execution(* com.itheima.controller.*.update(..))")
public Object logOperation(ProceedingJoinPoint joinPoint) throws Throwable {
    String className = joinPoint.getTarget().getClass().getName();
    String methodName = joinPoint.getSignature().getName();
    Object[] args = joinPoint.getArgs();
    long startTime = System.currentTimeMillis();
    Object result = joinPoint.proceed();
    long endTime = System.currentTimeMillis();
    // 这里可以将日志信息存入数据库,实际代码需要引入数据库操作相关依赖和代码
    return result;
}

  • 获取当前登录员工信息:员工登录成功后,信息存储在下发给客户端浏览器的 jwt 令牌中。在令牌校验过滤器TokenFilter中解析 jwt 令牌获取当前登录员工的 ID,利用ThreadLocal将 ID 传递给 AOP 程序、Controller、Service。
// 定义ThreadLocal操作的工具类
public class LoginEmployeeUtil {
    private static final ThreadLocal loginEmployeeId = new ThreadLocal<>();

    public static void setLoginEmployeeId(Long id) {
        loginEmployeeId.set(id);
    }

    public static Long getLoginEmployeeId() {
        return loginEmployeeId.get();
    }

    public static void removeLoginEmployeeId() {
        loginEmployeeId.remove();
    }
}

// 在TokenFilter中解析完ID后存入ThreadLocal
public class TokenFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 解析jwt令牌获取员工ID
        Long employeeId = parseJwtToken(servletRequest);
        LoginEmployeeUtil.setLoginEmployeeId(employeeId);
        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            LoginEmployeeUtil.removeLoginEmployeeId();
        }
    }
}

// 在AOP程序中从ThreadLocal获取当前登录员工的ID
@Around("execution(* com.itheima.controller.*.save(..)) || execution(* com.itheima.controller.*.delete(..)) || execution(* com.itheima.controller.*.update(..))")
public Object logOperation(ProceedingJoinPoint joinPoint) throws Throwable {
    Long employeeId = LoginEmployeeUtil.getLoginEmployeeId();
    // 其他记录日志操作...
    return joinPoint.proceed();
}

通过以上步骤,我们就可以基于 Spring AOP 实现记录系统增、删、改功能接口的操作日志。

五、总结

Spring AOP 在开发中有着广泛的应用场景,如记录系统操作日志、权限控制、事务管理等。通过本文对 Spring AOP 基础概念、快速入门、进阶知识以及案例实战的介绍,希望大家对 Spring AOP 有更深入的理解和掌握,能够在实际项目中灵活运用这一技术,提高代码的质量和开发效率。

你可能感兴趣的:(java,mybatis,学习,springboot,spring)