Skip to content

第四章:Spring AOP(面向切面编程)

1. AOP 概念及 Spring 中的应用

  • AOP 是什么

    • AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,用于将横切关注点(如日志记录、事务管理)与核心业务逻辑分离。

    • 通过 AOP,可以将这些横切关注点模块化,避免代码重复。

  • AOP 的核心概念

    • 切面(Aspect):横切关注点的模块化,通常是一个类。

    • 连接点(Join Point):程序执行过程中的某个点,如方法调用或异常抛出。

    • 通知(Advice):在连接点执行的动作,如前置通知、后置通知等。

    • 切入点(Pointcut):定义哪些连接点会触发通知。

    • 目标对象(Target Object):被代理的对象。

    • 代理(Proxy):由 Spring AOP 创建的对象,用于增强目标对象。

  • Spring 中的 AOP 实现

    • Spring AOP 基于动态代理实现,支持方法级别的拦截。

    • 默认使用 JDK 动态代理(基于接口),如果目标对象没有实现接口,则使用 CGLIB 代理。

2. 使用 AOP 实现事务管理、日志记录等

  • 常用通知类型

    • 前置通知(Before Advice):在目标方法执行前执行。

    • 后置通知(After Advice):在目标方法执行后执行,无论是否抛出异常。

    • 返回通知(After Returning Advice):在目标方法成功返回后执行。

    • 异常通知(After Throwing Advice):在目标方法抛出异常后执行。

    • 环绕通知(Around Advice):在目标方法执行前后都执行,可以控制是否执行目标方法。

  • 代码示例

java
// 定义一个切面类
@Aspect
@Component
public class LoggingAspect {

    // 前置通知
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("Before method: " + joinPoint.getSignature().getName());
    }

    // 返回通知
    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
        System.out.println("After returning from method: " + joinPoint.getSignature().getName());
        System.out.println("Result: " + result);
    }

    // 异常通知
    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
    public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
        System.out.println("After throwing from method: " + joinPoint.getSignature().getName());
        System.out.println("Exception: " + ex.getMessage());
    }

    // 环绕通知
    @Around("execution(* com.example.service.*.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("Before proceeding method: " + proceedingJoinPoint.getSignature().getName());
        Object result = proceedingJoinPoint.proceed();
        System.out.println("After proceeding method: " + proceedingJoinPoint.getSignature().getName());
        return result;
    }
}

3. 动态代理的实现原理

  • JDK 动态代理

    • 基于接口实现,通过 java.lang.reflect.Proxy 创建代理对象。

    • 要求目标对象必须实现至少一个接口。

  • CGLIB 动态代理

    • 基于继承实现,通过生成目标对象的子类来创建代理对象。

    • 不要求目标对象实现接口。

  • 代码示例

java
// JDK 动态代理示例
public class JdkProxyExample {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            (proxy1, method, args1) -> {
                System.out.println("Before method: " + method.getName());
                Object result = method.invoke(target, args1);
                System.out.println("After method: " + method.getName());
                return result;
            }
        );
        proxy.printUser();
    }
}

在Spring AOP的上下文调用

JoinPointProceedingJoinPoint是用于访问方法调用上下文信息的接口。以下是它们的详细介绍:

JoinPoint

JoinPoint接口提供了对连接点(即被拦截的方法调用)的访问。你可以在前置通知、后置通知和异常通知中使用它。常用的方法包括:

  • getArgs(): 返回方法参数的数组。

  • getSignature(): 返回被调用方法的签名信息。

  • getTarget(): 返回目标对象(即被代理的对象)。

  • getThis(): 返回AOP代理对象。

示例:

java
@Before("performTaskPointcut()")
public void beforeTask(JoinPoint joinPoint) {
    System.out.println("方法签名: " + joinPoint.getSignature());
    System.out.println("方法参数: " + Arrays.toString(joinPoint.getArgs()));
}

ProceedingJoinPoint

ProceedingJoinPoint是JoinPoint的子接口,专用于环绕通知。它提供了proceed()方法,用于继续执行被拦截的方法。你可以在环绕通知中使用它来控制方法的执行。

  • proceed(): 执行目标方法。

  • proceed(Object[] args): 使用新的参数执行目标方法。

示例:

java
@Around("performTaskPointcut()")
public Object aroundTask(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("方法签名: " + joinPoint.getSignature());
    System.out.println("方法参数: " + Arrays.toString(joinPoint.getArgs()));
    Object result = joinPoint.proceed(); // 执行目标方法
    System.out.println("返回值: " + result);
    return result;
}

通过这些接口,你可以在AOP通知中获取丰富的上下文信息,帮助你在方法执行前后进行各种操作。

课后练习:Spring AOP 实战

练习目标

通过编写代码,深入理解 Spring AOP 的使用场景,并掌握如何通过 AOP 实现日志记录和性能监控。

练习步骤

  1. 创建项目

    • 使用 Spring Initializr 创建一个新的 Spring Boot 项目,添加 Spring Web 和 Spring AOP 依赖。
  2. 实现日志记录

    • 创建一个切面类 LoggingAspect,使用前置通知和后置通知记录方法的调用和返回结果。
  3. 实现性能监控

    • 在 LoggingAspect 中添加环绕通知,计算方法的执行时间并输出。
  4. 测试 AOP 功能

    • 创建一个 UserService 类,定义几个方法,并测试日志记录和性能监控功能。

参考代码

java
// LoggingAspect.java
@Aspect
@Component
public class LoggingAspect {

    // 前置通知
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("Before method: " + joinPoint.getSignature().getName());
    }

    // 后置通知
    @After("execution(* com.example.service.*.*(..))")
    public void afterAdvice(JoinPoint joinPoint) {
        System.out.println("After method: " + joinPoint.getSignature().getName());
    }

    // 环绕通知(性能监控)
    @Around("execution(* com.example.service.*.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        long endTime = System.currentTimeMillis();
        System.out.println("Method " + proceedingJoinPoint.getSignature().getName() + " executed in " + (endTime - startTime) + "ms");
        return result;
    }
}

// UserService.java
@Service
public class UserService {
    public void printUser() {
        System.out.println("User: John Doe");
    }

    public void simulateLongTask() throws InterruptedException {
        Thread.sleep(2000);
        System.out.println("Long task completed");
    }
}

// MainApp.java
@SpringBootApplication
public class MainApp {
    public static void main(String[] args) throws InterruptedException {
        ApplicationContext context = SpringApplication.run(MainApp.class, args);
        UserService userService = context.getBean(UserService.class);

        userService.printUser();
        userService.simulateLongTask();
    }
}

练习总结

通过完成这个练习,你将:

  1. 掌握 Spring AOP 的基本概念和使用方法。

  2. 学会通过 AOP 实现日志记录和性能监控。

  3. 理解动态代理的实现原理。