深入解析 Spring AOP
一、AOP
1. 什么是AOP
AOP是一种编程范式,旨在通过分离关注点(Separation of Concerns)来提高代码的模块化程度。它允许将横切关注点(cross-cutting concerns)从业务逻辑中抽离出来,进而实现代码的解耦与复用。AOP 在 Spring 框架中应用非常广泛,主要用于解耦日志记录、安全检查、事务管理等横切关注点。
通俗解释:
AOP可以理解为一种帮助我们“加特技”到现有代码中的技术,而不需要直接改动原始代码。
假设你有一台机器,它能做很多事情,比如打印、计算、检查库存等等。每次机器完成任务时,你都希望记录日志、检查权限、统计耗时、进行事务管理等,这些操作与机器的核心功能(比如计算)是分开的,但它们每次都需要做。
现在,你的机器每次执行这些操作时,都会包含重复的日志、权限检查、耗时统计等代码。如果有很多地方都需要加这些功能,代码就会变得非常冗长和重复。
AOP 就是帮你解决这个问题的“特效工具”。你可以把日志、权限检查等功能定义为“特效”,然后在机器的每个操作中自动加上这些特效,而不需要每次都手动写重复的代码。
2. AOP 的核心概念
-
切面(Aspect):
切面是 AOP 编程的核心,它是一个模块化的关注点。通常,切面会在多个类中应用。比如,事务管理、日志记录、性能监控等都可以作为切面存在。
切面可以通过定义增强(advice)和切入点(pointcut)来实现。 -
连接点(JoinPoint):
连接点指的是程序执行中的某个点,比如方法调用、方法执行、构造函数执行等。在 AOP 中,切面是可以在这些连接点上执行的。 -
通知(Advice):
通知是切面要执行的具体操作。它是在连接点上运行的代码,主要有以下几种类型:- 前置通知(Before):在目标方法执行之前执行。
- 后置通知(After):在目标方法执行之后执行。
- 返回通知(After Returning):在目标方法正常返回时执行。
- 异常通知(After Throwing):在目标方法抛出异常时执行。
- 环绕通知(Around):它是最强大的通知,可以在方法调用前后都进行增- 强,甚至可以控制是否执行目标方法。
-
切入点(Pointcut):
切入点定义了在哪些连接点上执行通知。通过表达式来指定切入点,通常是方法的签名或注解等。例如,你可以选择只在特定包或类中的方法上应用通知。
在 Spring 中,切入点常通过 AspectJ 表达式语言定义,如:execution(* com.example.service.*.*(..))
。 -
织入(Weaving):
织入是将切面(Advice)应用到目标对象(JoinPoint)的过程。织入的方式通常有三种:- 编译时织入(Compile-time weaving):f在编译阶段进行织入。
- 类加载时织入(Load-time weaving):在类加载时进行织入。
- 运行时织入(Runtime weaving):在程序运行时进行织入。Spring AOP 就是通过运行时织入的方式来实现的。
3. AOP 在 Spring 中的实现:
Spring AOP 是基于代理模式(Proxy Pattern)实现的,它不直接修改目标对象的字节码,而是通过动态代理来对目标对象进行增强。Spring AOP 主要分为两种代理方式:
- JDK 动态代理:基于接口的代理,适用于目标对象实现了接口的情况。
- CGLIB 动态代理:基于类的代理,适用于目标对象没有实现接口的情况。CGLIB 使用字节码生成技术,在运行时创建目标类的子类,并通过子类代理目标类。
二、装配AOP
1. 如何装配
配置AOP的注解有以下这几个@Before(前置通知) @After(后置通知) @AfterReturning (返回通知)@AfterThrowing(异常通知) @Around(环绕通知)
在这些注解中,会见到一个参数,形如execution(* com.example.service.*.*(..))
,这表示匹配 com.example.service
包中的所有方法,当这里的其中一个或多个方法执行时,就会自动调用这几个注解所对应的函数,具体调用时机就看我们是如何选择的了。
2. 装配时机
Spring AOP 的装配时机可以简要总结为以下几种:
-
方法执行前(Before):
- 在目标方法执行之前执行通知。
- 用途:日志记录、权限验证、事务管理等。
- 示例:
@Before("execution(* com.example.service.OrderService.processOrder(..))")
-
方法执行后(After):
- 在目标方法执行完后执行通知。
- 用途:清理操作、释放资源、记录执行结果等。
- 示例:
@After("execution(* com.example.service.OrderService.processOrder(..))")
-
方法执行返回值(AfterReturning):
- 在目标方法成功返回后执行通知。
- 用途:后处理返回值,如修改返回结果。
- 示例:
@AfterReturning(pointcut = "execution(* com.example.service.OrderService.processOrder(..))", returning = "result")
-
方法执行异常(AfterThrowing):
- 当目标方法抛出异常时执行通知。
- 用途:异常处理、错误记录等。
- 示例:
@AfterThrowing(pointcut = "execution(* com.example.service.OrderService.processOrder(..))", throwing = "exception")
-
方法执行周围(Around):
- 可以在目标方法执行之前和之后都执行,甚至可以控制目标方法是否执行。
- 用途:性能监控、事务管理等。
- 示例:
@Around("execution(* com.example.service.OrderService.processOrder(..))")
三、AOP 用法示例
1. 日志记录
日志记录是 AOP 最常见的应用之一。假设你想在每个方法执行前后打印日志。
- Step 1: 定义一个日志切面(
LoggingAspect
)。
// src/main/java/com/example/aspect/LoggingAspect.java
package com.example.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// 前置通知:在方法执行之前记录日志
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint)