AOP 与 Spring 事务 学习笔记
关于 AOP 和 Spring 事务,我理解如下:
什么是 AOP?
AOP,全称 Aspect - Oriented Programming,中文译为 “面向切面编程”。它是一种编程范式,旨在通过分离横切关注点 (Cross - Cutting Concerns) 来提高软件系统的模块化程度。
想象一下,在我们的应用程序中,有很多功能模块(比如用户管理、订单管理、商品管理等)。除了这些核心业务逻辑之外,还存在一些通用的、分散在各个模块中的功能,例如:
- 日志记录 (Logging):记录方法的调用、参数、返回值等。
- 权限校验 (Authorization):检查用户是否有权限执行某个操作。
- 性能监控 (Performance Monitoring):记录方法的执行时间。
- 事务管理 (Transaction Management):确保一组数据库操作要么全部成功,要么全部失败。
这些功能就像 “切面” 一样,横向地 “切割” 了我们纵向的业务模块。如果我们在每个业务模块中都重复编写这些代码,会导致代码冗余、难以维护。
AOP 的核心思想就是将这些横切关注点从业务逻辑中剥离出来,形成独立的模块(即 “切面”),然后通过某种方式(通常是动态代理)将这些切面 “织入” 到业务逻辑的特定连接点上。
核心概念:
- 切面 (Aspect):一个模块,它封装了特定的横切关注点。例如,一个日志切面、一个事务切面。
- 通知 (Advice):切面在特定连接点上执行的动作。常见的通知类型有:
@Before
:在目标方法执行之前执行。@After
:在目标方法执行之后执行(无论成功还是失败)。@AfterReturning
:在目标方法成功执行之后执行。@AfterThrowing
:在目标方法抛出异常之后执行。@Around
:环绕目标方法执行,可以控制目标方法的执行。
- 连接点 (Join Point):程序执行过程中的某个特定点,例如方法的调用、异常的抛出等。在 Spring AOP 中,连接点通常指方法的执行。
- 切点 (Pointcut):一个表达式,用于匹配一个或多个连接点。它定义了通知应该在哪些连接点上执行。
- 目标对象 (Target Object):被一个或多个切面所通知的对象。也就是包含主要业务逻辑的类。
- 织入 (Weaving):将切面应用到目标对象并创建新的代理对象的过程。织入可以在编译时、加载时或运行时进行。Spring AOP 在运行时进行织入。
- 代理 (Proxy):AOP 框架创建的对象,用于封装目标对象并实现切面逻辑。客户端直接与代理对象交互。
AOP 的优势:
- 降低耦合度:将通用功能与业务逻辑分离,使代码更加清晰和易于维护。
- 提高代码复用性:可以将通用的横切关注点封装成切面,在多个地方重复使用。
- 增强模块化:每个模块专注于自身的核心功能。
你们项目中有没有使用到 AOP?
回答思路:
- 肯定回答:大概率是有的,因为 Spring 框架本身就大量使用了 AOP。
- 举例说明:结合项目实际情况,列举 1 - 2 个具体的应用场景,并给出简单的代码示例。
- 突出作用:说明 AOP 在这些场景中解决了什么问题,带来了什么好处。
回答范例:
“是的,面试官,我们项目中广泛地使用了 AOP。
最典型的应用场景有两个:
- 统一日志记录:我们通过 AOP 实现了一个全局的日志切面。这个切面会拦截所有 Service 层方法的调用,在方法执行前后记录方法的入参、出参以及执行耗时。这样做的好处是,我们不需要在每个 Service 方法中手动编写日志记录代码,大大减少了代码冗余,并且使得日志格式和记录时机保持一致,方便后续的问题排查和性能分析。
- 简单案例代码:
|
|
|
|
- 要使上述 AOP 生效,还需要在 Spring 配置类中开启 AOP 支持:
|
|
- 声明式事务管理:Spring 框架提供的声明式事务管理本身就是基于 AOP 实现的。我们在 Service 层的方法上使用
@Transactional
注解,Spring 就会自动为这些方法应用事务管理。当方法开始执行时,AOP 会开启事务;当方法执行完毕时,根据执行结果(成功或异常)自动提交或回滚事务。这使得我们的业务代码不再需要关注事务的开启、提交和回滚等细节,更加专注于业务逻辑本身,提高了开发效率和代码的可读性。- 概念性代码说明 (非完整运行示例,重点在 AOP 思想):
|
|
- 在这个例子中,
@Transactional
注解就是一个 “切点”,告诉 Spring AOP 在哪里应用事务管理的 “切面”。事务的开启、提交、回滚等逻辑就是 “通知”。
除此之外,我们可能还在权限校验、缓存处理等方面考虑过或使用了 AOP 的思想,将这些非业务核心但又普遍存在的功能进行模块化管理。”
Spring 中的事务是如何实现的?
Spring 中的事务管理,尤其是声明式事务,是基于 AOP 和 ThreadLocal 实现的。
核心原理:
- AOP 代理:
- 当一个 Bean 被声明为需要事务管理时(例如,通过
@Transactional
注解),Spring 容器在创建这个 Bean 的时候,并不会直接返回原始的 Bean 实例,而是会为它创建一个代理对象 (Proxy)。 - 这个代理对象包裹 (wraps) 了原始的 Bean 实例。
- 当客户端调用 Bean 中被
@Transactional
注解标记的方法时,实际上是调用了代理对象的相应方法。
- 当一个 Bean 被声明为需要事务管理时(例如,通过
- 事务拦截器 (TransactionInterceptor):
- 代理对象的方法内部会包含一个事务拦截器 (例如
TransactionInterceptor
)。这个拦截器就是 AOP 中的通知 (Advice)。 - 在目标业务方法执行之前 (类似
@Before
通知),事务拦截器会介入。 - 它会检查当前是否存在事务,如果不存在,则根据
@Transactional
注解的配置(如传播行为、隔离级别、只读属性等)开启一个新的数据库事务。
- 代理对象的方法内部会包含一个事务拦截器 (例如
- ThreadLocal 存储数据库连接 (Connection):
- 为了保证同一个事务中的所有数据库操作都使用同一个数据库连接,Spring 会将当前事务对应的数据库连接 (Connection) 存储在
ThreadLocal
变量中。 ThreadLocal
为每个线程都提供了一个独立的变量副本,这意味着在一个线程的执行过程中,无论调用多少个方法,只要它们在同一个事务内,就能获取到同一个数据库连接。这解决了多线程环境下事务管理的问题,保证了事务的线程安全性。
- 为了保证同一个事务中的所有数据库操作都使用同一个数据库连接,Spring 会将当前事务对应的数据库连接 (Connection) 存储在
- 业务方法执行:
- 开启事务后,代理对象会调用原始 Bean 实例的业务方法,执行实际的业务逻辑和数据库操作。
- 由于数据库连接是从
ThreadLocal
中获取的,所以业务方法中的所有 DAO 操作都会在同一个事务上下文中执行。
- 事务提交或回滚:
- 当业务方法执行完毕后,事务拦截器会再次介入 (类似
@AfterReturning
或@AfterThrowing
通知)。 - 如果业务方法正常执行完成,没有抛出任何(未被捕获的、符合回滚规则的)异常,事务拦截器会提交事务。
- 如果业务方法抛出了异常 (通常是
RuntimeException
或其子类,或者在@Transactional
中配置了rollbackFor
指定的异常),事务拦截器会回滚事务。
- 当业务方法执行完毕后,事务拦截器会再次介入 (类似
- 关闭连接与清理:
- 事务提交或回滚之后,事务拦截器会负责关闭数据库连接 (通常是将其归还给连接池),并清理
ThreadLocal
中存储的连接信息。
- 事务提交或回滚之后,事务拦截器会负责关闭数据库连接 (通常是将其归还给连接池),并清理
总结 Spring 事务实现的关键点:
- 基于 AOP:通过动态代理和拦截器机制,在业务代码执行前后动态地添加事务管理逻辑。
@Transactional
注解:提供了一种声明式的方式来定义事务的边界和属性,简化了事务管理。PlatformTransactionManager
接口:Spring 事务管理的核心接口,定义了事务操作(获取事务、提交、回滚)的规范。具体的实现类(如DataSourceTransactionManager
对应 JDBC,JpaTransactionManager
对应 JPA)负责与底层数据访问技术交互。TransactionDefinition
接口:定义事务的属性,如传播行为 (Propagation Behavior)、隔离级别 (Isolation Level)、超时时间 (Timeout)、只读状态 (Read - only) 等。@Transactional
注解的属性最终会转换为TransactionDefinition
的配置。TransactionStatus
接口:表示当前事务的状态,可以用来控制事务的提交和回滚。ThreadLocal
:确保了在单个线程中,事务操作使用的是同一个数据库连接,从而保证了事务的原子性和一致性。
简单案例思路 (伪代码):
|
|
Spring AOP 和事务管理大致流程
|
|