1.基本概念
AOP即面向切面编程,它利用的是一种横切技术,解剖开封装的对象内部,并将那些影响多个类的公共行为封装到一个可重 用模块,这就是所谓的Aspect方面/切面。所谓的切面,简单点所说,就是将哪些与业务无关,却为业务 模块所共同调用的行为(方法)提取封装,减少系统的重复代码,以达到逻辑处理过程中各部分之间低 耦合的隔离效果。
Spring AOP 的实现基于动态代理技术(JDK 或 CGLIB),但为了简化开发,它引入了 AspectJ 的注解模型(如@Aspect
、@Pointcut
等),从而支持更简洁的切面定义。
2.AOP相关术语
切面:切面就是对横切关注点进行模块化封装的一种方式,在SpringAop中就是一个类。例如要实现一个登录页面的功能,但是我想要在登录前进行权限的验证,判断你是管理员还是用户,此时不可能直接把这个功能直接写在登录页面的功能里,会提高耦合性,所以就可以另写一个类将权限验证的功能封装。
在SpringAop中用@Aspect对相关类标记为切面。
连接点:连接点指的是程序执行过程中可以插入切面的特定位置。在 Spring AOP 中,连接点仅支持方法执行这一种情况,也就是在某个方法调用或者方法执行前后这些时间点。
还是上面的例子,当你准备执行登录的方法时,该方法就是一个连接点,切面中实现的登录验证就可以在该方法前执行。
切点:切点是用于匹配连接点的表达式,它定义了哪些连接点会被应用通知。通过切点表达式,可以精确地筛选出需要增强的方法。它就是用来定位那些被增强的方法,使得通知能够准确的应用到目标方法(也就是这些被增强的方法)
这里的被增强的方法就是那些被切面分装了相关方法准备在该方法前后执行,就比如登录方法多了个权限验证功能,那么这个登录方法就是被增强的方法。
spring中用@Pointcut来指定相应的包进行扫描,来定位包中的方法是否为被增强的方法,它有一个切点表达式,来负责筛选被增强的方法。
切点表达式的定义如下
@Pointcut(“execution([修饰符] 返回类型 [包名..][类名.]方法名([参数]))”)
修饰符:可选(如public
、private
),可省略。
返回类型:必须,用*
表示任意返回类型。
包名:com.example.service
表示特定包;com.example..
表示递归匹配子包。
类名:UserService
表示特定类;*Service
表示类名以Service
结尾。
方法名:saveUser
表示特定方法;*
表示所有方法。
参数:(User)
表示参数为User
类型;(..)
表示任意数量参数。
通知:通知就是规定在切点即被增强的方法执行方法的逻辑,就比如前面说到的又在登录功能前加上一个权限验证的方法,通知就是用来规定权限验证功能必须在登录前执行,而不是登录后执行,也就是规定了执行的逻辑。
下面列举几种通知的类型(结合spring的注解)
@Before
:前置通知,就是在切点执行前执行
@AfterReturning
:返回后通知,就是在切点正常执行后执行
@AfterThrowing
:异常后通知,就是在切点执行时出现异常时执行
@After
:后置通知(最终通知),无论切点是正常执行还时执行时出现异常,它都会在他后执行,相当于是个收尾的执行。
@Around
:环绕通知,它会完全包裹切点的执行,可以在它执行前后进行执行,甚至可以控制切点执行与否以及如何执行,也就是说它对切点执行前后都有控制权。
目标对象:目标对象指的是包含核心业务逻辑的原始对象,也就是被一个或多个切面所增强的对象。简单来说,就是我们希望对其方法进行额外功能增强(如添加日志记录、事务管理等)的那个对象。
代理:代理是指 Spring AOP 为目标对象生成的一个包装对象,该对象与目标对象具有相同的接口(对于 JDK 动态代理)或者继承自目标对象(对于 CGLIB 代理)。代理对象会拦截对目标对象方法的调用,并在调用前后插入切面中定义的通知逻辑,从而实现对目标对象方法的增强。
在 Spring AOP 中,通过@EnableAspectJAutoProxy
注解启用自动代理功能,Spring 会根据目标对象是否实现接口来选择使用 JDK 动态代理还是 CGLIB 代理。也可以通过proxyTargetClass
属性强制使用 CGLIB 代理:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用CGLIB代理
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}
织入:
织入是 AOP 的核心步骤,它将切面中定义的切点(Pointcut)和通知(Advice)与目标对象的方法进行关联,使得在目标方法执行时,通知逻辑能够被触发。
织入的时机
AOP 框架的织入可以发生在以下阶段:
编译时(Compile-time Weaving):在目标类编译时进行织入(如 AspectJ)。
类加载时(Load-time Weaving):在目标类被加载到 JVM 时进行织入(如 AspectJ 的 LTW)。
运行时(Runtime Weaving):在程序运行时通过动态代理动态织入(如 Spring AOP)。
引入:
引入本质上是一种类型级别的增强,它打破了传统面向对象编程中类的功能固定性,通过 AOP 的方式,在运行时为类添加额外的接口实现,从而让类拥有新的行为和状态。
在 Spring AOP 中,引入主要通过 @DeclareParents
注解来实现。该注解可以指定一个接口,让目标类实现这个接口,并提供一个默认的实现类,从而为目标类添加新的方法。
使用上面那些注解时需要现在xml文件中开启spring对aop的注解支持
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
要把它定义再bean标签中
3.SpringAOP实现事务管理
当我们要对数据库进行CRUD操作时,如果不对其进行事务管理,那么数据库无疑就是在“裸奔”,会出现各种各样的问题,如事务一致性,原子性等遭到破坏,在spring中也是同样的,只要涉及到对数据库的操作时,事务管理是必要的的。
在具体讲之前,先回忆一下数据库的基础事务操作
START:开启事务,显示启动事务,后续的sql操作会被视为事务的一部分
COMMIT:提交事务,将所有的操作保存到数据库中,提交后,数据不可变更,不允许滚回。
ROLLBACK:回滚事务,撤销事务中所有未提交的操作,回滚到事务开启前的状态。
SAVEPOINT:保存点,在事务中设置中间保存点,允许部分回滚到该点。
下面详细讲解具体的操作
3.1maven相关依赖配置
具体依赖如下
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>spring-transaction-aop-example</artifactId><version>1.0-SNAPSHOT</version><properties><!-- Spring 版本 --><spring.version>6.1.4</spring.version><!-- AspectJ 版本 --><aspectj.version>1.9.20</aspectj.version><!-- MySQL 驱动版本 --><mysql.version>8.2.0</mysql.version></properties><dependencies><!-- Spring Context --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><!-- Spring AOP --><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>${spring.version}</version></dependency><!-- Spring Transaction --><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>${spring.version}</version></dependency><!-- AspectJ Weaver --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>${aspectj.version}</version></dependency><!-- Spring JDBC --><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><!-- MySQL 数据库驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.11.0</version><configuration><source>17</source><target>17</target></configuration></plugin></plugins></build>
</project>
3.2配置数据源
在spring相关的xml文件中的bean标签内配置相关数据库的具体信息,以便能够连上数据库。
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/test"/><property name="username" value="root"/><property name="password" value="password"/>
</bean>
写在这里比直接写在具体的代码中更加方便后期的修改与维护,而不用直接在代码中去修改。
3.3配置事务资源管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/>
</bean>
它这里将数据源注入了事务资源管理器,这样事务资源管理器就知道从哪个数据源获取数据库连接来进行事务管理。
3.4配置事务管理
<!-- 启用声明式事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
在需要管理的业务方法上添加@Transactional,基础使用如下
@Transactional
public void saveOrder() {// 数据库操作(增删改)orderDao.insert(order);userDao.updateBalance(userId, amount);
}
他会在方法正常执行后自动提交,出现异常时自动回滚。
扩展配置如下:通过相关参数来定制事务
propagation | 事务传播行为(默认REQUIRED ) |
isolation | 事务隔离级别(默认DEFAULT ) |
rollbackFor | 指定需要回滚的异常类型(如SQLException.class ) |
timeout | 事务超时时间(秒) |
readOnly | 是否为只读事务(默认false ) |
@Transactional(propagation = Propagation.REQUIRES_NEW, // 强制新建事务isolation = Isolation.SERIALIZABLE, // 最高隔离级别rollbackFor = Exception.class, // 回滚所有异常timeout = 30 // 超时时间30秒
)
public void criticalOperation() { ... }
3.5注意事项
- 注解作用域:
@Transactional
仅对public
方法有效。 - 内部调用问题:类内部方法调用被注解的方法时,事务不会生效(需通过代理对象调用)。
3.6底层原理
Spring 通过AOP 动态代理实现事务织入:
- 代理创建:Spring 为目标类生成代理对象(JDK 或 CGLIB 代理)。
- 方法拦截:代理对象拦截被
@Transactional
标记的方法调用。 - 事务逻辑:
- 调用方法前:开启事务(
beginTransaction
)。 - 方法正常执行后:提交事务(
commit
)。 - 方法抛出异常时:回滚事务(
rollback
)。
- 调用方法前:开启事务(