第四章: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):在目标方法执行前后都执行,可以控制是否执行目标方法。
代码示例
// 定义一个切面类
@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 动态代理
基于继承实现,通过生成目标对象的子类来创建代理对象。
不要求目标对象实现接口。
代码示例
// 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的上下文调用
JoinPoint和ProceedingJoinPoint是用于访问方法调用上下文信息的接口。以下是它们的详细介绍:
JoinPoint
JoinPoint接口提供了对连接点(即被拦截的方法调用)的访问。你可以在前置通知、后置通知和异常通知中使用它。常用的方法包括:
getArgs(): 返回方法参数的数组。
getSignature(): 返回被调用方法的签名信息。
getTarget(): 返回目标对象(即被代理的对象)。
getThis(): 返回AOP代理对象。
示例:
@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): 使用新的参数执行目标方法。
示例:
@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 实现日志记录和性能监控。
练习步骤
创建项目
- 使用 Spring Initializr 创建一个新的 Spring Boot 项目,添加 Spring Web 和 Spring AOP 依赖。
实现日志记录
- 创建一个切面类 LoggingAspect,使用前置通知和后置通知记录方法的调用和返回结果。
实现性能监控
- 在 LoggingAspect 中添加环绕通知,计算方法的执行时间并输出。
测试 AOP 功能
- 创建一个 UserService 类,定义几个方法,并测试日志记录和性能监控功能。
参考代码
// 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();
}
}
练习总结
通过完成这个练习,你将:
掌握 Spring AOP 的基本概念和使用方法。
学会通过 AOP 实现日志记录和性能监控。
理解动态代理的实现原理。