Spring AOP:通过注解与XML实现横切关注点
面向切面编程(Aspect-Oriented Programming, AOP)是面向对象编程(OOP)的一种补充,它旨在将横切关注点(如日志、事务、安全)与业务逻辑代码解耦。通过AOP,我们可以在不修改原有业务代码的情况下,动态地为这些代码织入额外的功能,从而提高代码的模块化和可维护性。AOP的核心思想是代理模式,它允许我们在目标对象的方法执行前后或异常时插入自定义逻辑。
AOP的核心概念包括:
- 切面(Aspect):一个模块化的关注点,例如日志记录。在Spring中,切面通常是一个带有
@Aspect注解的类。 - 连接点(Joinpoint):程序执行流中一个明确的点,例如一个方法的调用或异常的抛出。
- 通知(Advice):在切面中定义的,在特定连接点执行的动作。Spring支持五种类型的通知。
- 切入点(Pointcut):匹配连接点的断言或谓词。它定义了通知应该在哪些连接点上执行。
- 目标对象(Target Object):被一个或多个切面所通知的对象。Spring AOP通过代理对象来操作目标对象。
- 代理(Proxy):AOP框架创建的对象,用于实现切面的功能。Spring AOP默认使用JDK动态代理,如果目标对象没有实现接口,则使用CGLIB代理。
通知类型:
- 前置通知(Before Advice):在连接点之前执行。
- 后置通知(After Advice):在连接点退出时执行,无论结果是正常返回还是异常退出。
- 返回后通知(After Returning Advice):在连接点正常返回后执行。
- 环绕通知(Around Advice):包围一个连接点,可以在方法调用前后执行自定义逻辑,并决定是否继续执行目标方法。
- 异常后通知(After Throwing Advice):在方法抛出异常后执行。
Spring AOP可以通过注解或XML配置两种方式实现。下面我们将分别介绍这两种方式。
一、基于注解的实现
使用注解配置AOP,通常需要两个步骤:
- 在配置类上启用AOP自动代理功能。
- 创建一个切面类,并使用AOP注解定义切点和通知。
首先,我们创建一个Spring配置类:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
// 此处可以定义其他Bean
}
然后,我们创建一个切面类LoggingAspect,用于记录服务方法的执行日志:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
// 定义一个切入点,匹配com.example.service包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethod() {}
// 前置通知
@Before("serviceMethod()")
public void logBefore(JoinPoint joinPoint) {
logger.info("开始执行方法: {}", joinPoint.getSignature().getName());
}
// 后置通知
@After("serviceMethod()")
public void logAfter(JoinPoint joinPoint) {
logger.info("方法执行结束: {}", joinPoint.getSignature().getName());
}
// 返回后通知
@AfterReturning(pointcut = "serviceMethod()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
logger.info("方法 {} 执行成功,返回值: {}", joinPoint.getSignature().getName(), result);
}
// 异常后通知
@AfterThrowing(pointcut = "serviceMethod()", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Throwable ex) {
logger.error("方法 {} 执行异常: {}", joinPoint.getSignature().getName(), ex.getMessage());
}
// 环绕通知
@Around("serviceMethod()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed(); // 执行目标方法
long endTime = System.currentTimeMillis();
logger.info("方法 {} 执行耗时: {} ms", joinPoint.getSignature().getName(), (endTime - startTime));
return result;
} catch (Throwable e) {
long endTime = System.currentTimeMillis();
logger.error("方法 {} 执行异常,耗时: {} ms,异常信息: {}", joinPoint.getSignature().getName(), (endTime - startTime), e.getMessage());
throw e; // 重新抛出异常,确保异常继续传播
}
}
}
接下来,我们定义一个简单的服务类OrderService:
import org.springframework.stereotype.Service;
@Service
public class OrderService {
public String createOrder(String orderId) {
System.out.println("正在创建订单: " + orderId);
return "订单 " + orderId + " 创建成功";
}
public void cancelOrder(String orderId) {
System.out.println("正在取消订单: " + orderId);
// 模拟异常
throw new RuntimeException("订单 " + orderId + " 取消失败");
}
}
最后,我们编写一个测试类来验证AOP功能:
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AopDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
OrderService orderService = context.getBean(OrderService.class);
System.out.println("--- 测试正常方法 ---");
String result = orderService.createOrder("ORD-001");
System.out.println("创建结果: " + result);
System.out.println("\n--- 测试异常方法 ---");
try {
orderService.cancelOrder("ORD-002");
} catch (Exception e) {
System.out.println("捕获到异常: " + e.getMessage());
}
context.close();
}
}
二、基于XML的实现
如果偏好使用XML配置,我们可以在Spring的XML配置文件中定义切面、切入点和通知。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 定义服务Bean -->
<bean id="orderService" class="com.example.service.OrderService"/>
<!-- 定义切面Bean -->
<bean id="loggingAspect" class="com.example.aop.LoggingAspect"/>
<!-- AOP配置 -->
<aop:config>
<!-- 声明一个切面,并引用切面Bean -->
<aop:aspect id="logging" ref="loggingAspect">
<!-- 定义切入点 -->
<aop:pointcut id="serviceMethod" expression="execution(* com.example.service.*.*(..))"/>
<!-- 前置通知 -->
<aop:before pointcut-ref="serviceMethod" method="logBefore"/>
<!-- 后置通知 -->
<aop:after pointcut-ref="serviceMethod" method="logAfter"/>
<!-- 返回后通知 -->
<aop:after-returning pointcut-ref="serviceMethod" returning="result" method="logAfterReturning"/>
<!-- 异常后通知 -->
<aop:after-throwing pointcut-ref="serviceMethod" throwing="ex" method="logAfterThrowing"/>
<!-- 环绕通知 -->
<aop:around pointcut-ref="serviceMethod" method="logAround"/>
</aop:aspect>
</aop:config>
</beans>
切入点表达式
切入点表达式用于精确定位连接点。最常用的表达式是execution,其语法如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
modifiers-pattern?:方法修饰符(可选)。ret-type-pattern:返回值类型。declaring-type-pattern?:方法所在的类或接口(可选)。name-pattern:方法名。param-pattern:方法参数。throws-pattern?:方法抛出的异常(可选)。
一些常见的execution表达式示例:
execution(* com.example.service.*.*(..)):匹配com.example.service包及其子包下所有类的所有方法。execution(public * com.example.service.OrderService.create*(..)):匹配OrderService类中以create开头且为public的所有方法。execution(* com.example.service.OrderService.createOrder(String)):匹配OrderService类中名为createOrder且参数为String类型的方法。
通过合理使用AOP,我们可以有效地将横切关注点与核心业务逻辑分离,使代码更加清晰和易于维护。