目录
一、AOP 核心概念
1.1 基本术语
- 切面(Aspect):封装横切关注点的模块,包含通知和切点。
- 通知(Advice):切面在特定连接点执行的增强逻辑,包括:
- 前置通知(Before):方法调用前执行
- 后置通知(After):方法调用后执行(无论是否异常)
- 返回通知(AfterReturning):方法正常返回后执行
- 异常通知(AfterThrowing):方法抛出异常后执行
- 环绕通知(Around):包围方法调用,可自定义执行时机
- 切点(Pointcut):定义匹配连接点的规则,确定通知何时执行。
- 连接点(Join Point):程序执行过程中的特定点(如方法调用、异常抛出)。
- 织入(Weaving):将切面逻辑插入目标对象的过程,分为编译时、类加载时和运行时织入。
二、Spring AOP 实现原理
2.1 代理机制
Spring AOP 基于动态代理实现,支持两种代理方式:
2.1.1 JDK 动态代理
- 基于接口:代理对象实现目标接口,通过
InvocationHandler
拦截方法调用。 - 限制:只能代理实现了接口的类。
// JDK 动态代理示例
interface UserService {
void createUser(String username);
}
class UserServiceImpl implements UserService {
@Override
public void createUser(String username) {
System.out.println("Creating user: " + username);
}
}
// 代理处理器
class LoggingInvocationHandler implements InvocationHandler {
private final Object target;
public LoggingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After method: " + method.getName());
return result;
}
}
// 创建代理
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class<?>[]{UserService.class},
new LoggingInvocationHandler(target)
);
2.1.2 CGLIB 代理
- 基于子类:通过继承目标类生成子类,重写方法实现拦截。
- 限制:无法代理
final
类或final
方法。
// CGLIB 代理示例
class ProductService {
public void updatePrice(String productId, double price) {
System.out.println("Updating price for " + productId + " to " + price);
}
}
// 方法拦截器
class TransactionInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Begin transaction");
Object result = proxy.invokeSuper(obj, args);
System.out.println("Commit transaction");
return result;
}
}
// 创建代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ProductService.class);
enhancer.setCallback(new TransactionInterceptor());
ProductService proxy = (ProductService) enhancer.create();
2.1.3 代理选择策略
- 默认规则:
- 若目标对象实现了接口,使用 JDK 动态代理。
- 若目标对象未实现接口,使用 CGLIB 代理。
- 强制 CGLIB:通过
@EnableAspectJAutoProxy(proxyTargetClass = true)
配置。
2.2 AOP 处理流程
- 定义切面:通过
@Aspect
注解标记切面类,定义通知和切点。 - 创建代理:容器启动时,根据切点匹配规则为目标 Bean 创建代理对象。
- 方法调用:客户端调用代理对象的方法。
- 拦截处理:代理对象根据切面配置,在连接点执行通知逻辑。
- 目标方法执行:通知逻辑中调用目标方法或直接返回结果。
三、切点表达式详解
3.1 切点表达式语法
Spring AOP 使用 AspectJ 切点表达式语言,主要通配符:
*
:匹配任意数量字符(不含包路径分隔符)。..
:匹配任意数量字符(含包路径分隔符),或任意参数。+
:匹配指定类及其子类。
3.2 常见切点类型
3.2.1 方法执行(execution)
// 匹配 com.example.service 包下所有类的所有方法
execution(* com.example.service.*.*(..))
// 匹配 UserService 接口的所有方法
execution(* com.example.service.UserService.*(..))
// 匹配以 "get" 开头的方法
execution(* com.example.service.*.get*(..))
// 匹配返回类型为 String 的方法
execution(String com.example.service.*.*(..))
// 匹配带两个参数的方法
execution(* com.example.service.*.*(java.lang.String, int))
3.2.2 注解匹配(@annotation)
java
// 匹配带 @Transactional 注解的方法
@annotation(org.springframework.transaction.annotation.Transactional)
// 自定义注解示例
@annotation(com.example.annotation.Loggable)
3.2.3 类型匹配(within)
// 匹配指定包下的所有类
within(com.example.service.*)
// 匹配指定包及其子包下的所有类
within(com.example.service..*)
// 匹配特定类
within(com.example.service.UserServiceImpl)
3.2.4 Bean 名称匹配(bean)
// 匹配名称以 "service" 结尾的 Bean
bean(*Service)
// 匹配特定 Bean
bean(userService)
3.2.5 参数匹配(args)
// 匹配参数类型为 String 和 Integer 的方法
args(java.lang.String, java.lang.Integer)
// 匹配实现 Serializable 接口的参数
args(java.io.Serializable)
四、复合切点表达式
通过逻辑运算符组合多个切点:
&&
:与||
:或!
:非
// 匹配 UserService 接口的所有方法,且不带 @ReadOnly 注解
execution(* com.example.service.UserService.*(..)) && !@annotation(com.example.annotation.ReadOnly)
// 匹配 service 包下的所有方法,或带 @Loggable 注解的方法
within(com.example.service.*) || @annotation(com.example.annotation.Loggable)
五、通知参数绑定
通知方法可通过参数获取连接点信息:
5.1 JoinPoint
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
// 获取方法签名
Signature signature = joinPoint.getSignature();
// 获取方法参数
Object[] args = joinPoint.getArgs();
// 获取目标对象
Object target = joinPoint.getTarget();
}
5.2 返回值绑定(AfterReturning)
@AfterReturning(pointcut = "execution(* com.example.service.*.get*(..))", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
// 处理返回值
}
5.3 异常绑定(AfterThrowing)
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
// 异常处理
}
5.4 环绕通知(Around)
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 前置处理
Object result = joinPoint.proceed(); // 调用目标方法
// 后置处理
return result;
}
六、AOP 实现示例
6.1 定义切面
@Aspect
@Component
public class LoggingAspect {
// 定义切点
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
// 前置通知
@Before("serviceMethods()")
public void beforeServiceMethod(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
// 环绕通知
@Around("serviceMethods()")
public Object aroundServiceMethod(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println("Method " + joinPoint.getSignature().getName() + " executed in " + (endTime - startTime) + "ms");
return result;
}
}
6.2 启用 AOP
@SpringBootApplication
@EnableAspectJAutoProxy
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
七、AOP 应用场景
7.1 事务管理
@Transactional
public void transferFunds(String fromAccount, String toAccount, double amount) {
// 业务逻辑
}
7.2 权限验证
@Before("@annotation(com.example.annotation.RequiresAdmin)")
public void checkAdminPermission(JoinPoint joinPoint) {
// 验证用户权限
}
7.3 日志记录
@Around("execution(* com.example.repository.*.*(..))")
public Object logRepositoryMethods(ProceedingJoinPoint joinPoint) throws Throwable {
Logger logger = LoggerFactory.getLogger(joinPoint.getTarget().getClass());
logger.debug("Calling method: {}", joinPoint.getSignature().getName());
Object result = joinPoint.proceed();
logger.debug("Method returned: {}", result);
return result;
}
7.4 性能监控
@Around("execution(* com.example.service.*.*(..))")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.printf("Method %s.%s took %d ms%n",
joinPoint.getTarget().getClass().getName(),
joinPoint.getSignature().getName(),
endTime - startTime);
return result;
}
八、最佳实践
8.1 切点表达式优化
- 避免过于宽泛的切点:如
execution(* *(..))
,可能导致意外拦截。 - 使用命名切点:提高表达式复用性。
- 优先使用注解:通过自定义注解标记需要增强的方法。
8.2 通知类型选择
- 环绕通知慎用:功能强大但易出错,优先使用其他通知类型。
- 异常处理:确保
@AfterThrowing
通知捕获特定异常类型。
8.3 性能考虑
- 减少反射调用:环绕通知中避免多次调用
proceed()
。 - 代理创建开销:大量 Bean 被代理时可能影响启动性能。
8.4 调试技巧
- 启用调试日志:配置
org.springframework.aop
日志级别为 DEBUG。 - 使用
@Order
注解:控制多个切面的执行顺序。
九、总结
Spring AOP 通过动态代理机制实现横切关注点的模块化,结合 AspectJ 切点表达式提供了强大的方法拦截能力。理解其实现原理和切点表达式语法,有助于开发者在事务管理、权限控制、日志记录等场景中合理应用 AOP,提升代码的可维护性和复用性。