Spring AOP
(Aspect-Oriented Programming,面向切面编程)是 Spring
框架中的一个重要特性,用于实现横切关注点的模块化和重用。它通过将横切关注点(如日志记录、性能监控、事务管理等)从业务逻辑中分离出来,以便将它们应用到多个模块中的相同位置,实现代码的重用和解耦。
Spring AOP
的核心概念是切面(Aspect
)、连接点(Join Point
)、通知(Advice
)和切点(Pointcut
)。
- 切面:切面是一个跨越多个对象的模块化单元,它定义了横切关注点和它们的行为,一个切面可以包含多个通知和切点。
- 连接点:连接点是在应用程序执行过程中能够插入切面的点,通常连接点是方法的执行点,但也可以是异常抛出时或字段访问时等。
- 通知:通知是切面在连接点处执行的代码,可以在连接点之前、之后或周围执行,以实现不同的行为。常见的通知类型包括前置通知(在连接点之前执行)、后置通知(在连接点之后执行)、异常通知(在连接点抛出异常时执行)和环绕通知(在连接点前后都执行)。
- 切点:切点定义了在哪些连接点上应用通知,通过指定切点表达式或使用注解来确定切点的位置。
Spring AOP
可以通过 XML
配置、注解或基于 Java
的配置类来定义切面和通知。它可以与任何 Spring
管理的 bean
一起使用,包括业务对象、服务类、控制器等。在运行时 Spring AOP
使用动态代理技术将通知织入到目标对象的方法中,实现切面的功能。
通过使用 Spring AOP
,可以实现横切关注点的集中管理和复用,提高代码的可维护性和灵活性。它可以帮助开发人员解决各种横切关注点的需求,如日志记录、性能监控、事务管理、安全性检查等。
1. 切点定义
在 Spring
中通过 @Pointcut
定义切点,通过在类上使用 @Aspect
注解开启切面。其基本格式如下:
@Aspect
@Component
public class AspectConfig {
@Pointcut("<Action> (<Pattern> <Return> <Package>(<Params>))")
public void pointcut() {
}
}
上述各参数对应描述信息参考下表:
参数 | 说明 |
---|---|
Action | 表示作用类型,最常用的为 execution ,后续将详细介绍。 |
Pattern | 指定作用对象的修饰符,如方法的 public 、 private 等,为 * 表示所有。非必填,缺省时为 * 。 |
Return | 指定作用对象的返回类型,为 * 表示所有。 |
Package | 指定作用的对象,通常为方法类所在包,非必填,缺省时为 * 。 |
Params | 指定方法的行参类型,如 String 等,设置为 .. 表示允许零个或多个参数。 |
2. 切点分类
根据作用对象切点共分为下列几类,下面分别介绍各类定义方式
(1) 方法切点
execution
作用域细化到方式,也是最常用的方式,具体参数参考上一点的表格。
如下切点定义示例作用域为 xyz.ibudai.controller
包中所有类的方法。
@Pointcut("execution (public * xyz.ibudai.controller.*.*(..))")
public void pointcut() {
}
(2) 类切点
within
作用域细化到类,即只能监听类中所有方法,无法细化到指定方法。
如下示例定义了监控 StudentServiceImpl
类中所有方法。
@Pointcut("within(xyz.ibudai.service.Impl.StudentServiceImpl)")
public void pointcut() {
}
(3) 参数切点
args()
用于定义参数切面,其参数说明如下:
args()
: 不带参数。args(..)
: 任意参数。args(java.lang.String)
: 指定参数类型。
@Pointcut("args(..)")
public void pointcut() {
}
(4) 注解切点
通过 @annotation
定义注解切面,提供两种如下两种方式,第二种方式可以获取注解中的值更为常用。
@Pointcut("@annotation(xyz.ibudai.anntation.MethodAnn)")
public void pointcut1() {
}
@Pointcut("@annotation(methodAnn) && @annotation(xyz.ibudai.anntation.MethodAnn)")
public void pointcut2() {
}
@Around("pointcut2()")
public Object around2(ProceedingJoinPoint joinPoint, MethodAnn methodAnn) {
System.out.println("value 2: " + methodAnn.value());
}
3. 通知事件
通知是切面在连接点处执行的代码,可以在连接点之前、之后或周围执行,以实现不同的行为。
(1) @Before
前置通知在连接点之前执行。
@Before("pointcut()")
public void before(JoinPoint joinPoint) {
System.out.println("Before advice: " + joinPoint);
}
(2) @Around
环绕通知在连接点前后都执行,可获取请求目标的详细信息并通过 joinPoint.proceed()
放行请求。
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("Around advice");
// 作用类型
logger.info("Pointcut effect type: " + joinPoint.getKind());
// 作用域
logger.info("Pointcut effect range: " + joinPoint.toString());
// 切面目标类
logger.info("Pointcut target class: " + joinPoint.getTarget().getClass());
// 切面方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
logger.info("Pointcut method name: " + signature.getMethod().getName());
// 返回方法传入的参数
Object[] obj = joinPoint.getArgs();
logger.info("Pointcut receive parameter: " + Arrays.toString(obj));
try {
// 放行方法
return joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
(3) @AfterThrowing
异常通知在连接点抛出异常时执行。
@AfterThrowing(value = "pointcut()", throwing = "throwable")
public void afterThrowing(JoinPoint joinPoint, Exception throwable) {
logger.error("切面异常, 方法: {}", ((MethodSignature) joinPoint.getSignature()).getMethod().toString());
System.out.println("After throwing " + joinPoint + ", throw message: " + throwable.getMessage());
}
(4) @AfterReturning
后置通知,在目标方法成功执行并返回结果后执行,即在方法返回之后执行。
@AfterReturning(value = "pointcut()", returning = "obj")
public void afterReturning(JoinPoint joinPoint, Object obj) {
System.out.println("After returning, " + joinPoint + ", return: " + obj);
}
(5) @After
后置通知,无论目标方法是正常返回还是抛出异常,都会在方法执行之后执行,@AfterReturning
可以理解为 @After
的真子集。
@After
使用方式与 @AfterReturning
相同,仅作用域不同,这里不再提供示例。