前言:
在现代软件开发中,处理横切关注点(cross-cutting concerns)和管理事务是非常重要的技术。Spring框架提供了两个核心特性来处理这些问题:Spring AOP和Spring事务管理。本文将详细探讨Spring AOP和Spring事务管理的概念、原理以及如何在实际应用中应用它们。
- Spring AOP详解
1、Spring AOP简介
Spring AOP(Aspect-Oriented Programming)是一种编程风格,它旨在解决横切关注点的问题。它通过分析程序结构中的关注点,提供了一种补充面向对象编程(OOP)的方法,这就是面向切面编程(Aspect-Oriented Programming)。使用Spring AOP,我们可以将横切逻辑(例如日志记录、事务管理等)与核心业务逻辑分离开来,从而实现更好的模块化和复用性。
2、动态代理技术
(1)JDK代理:一种基于接口的动态代理技术,它通过反射机制动态地生成代理类来在被代理对象和代理对象之间进行通信。
(2)cglib代理:基于父类的动态代理技术。cglib代理不需要接口实现,它是直接对被代理对象进行子类的创建和扩展,从而生成代理类。由于是生成子类,所以被代理对象必须有可继承的父类。
3、AOP术语
(1)切面(Aspect):切面是横切关注点的封装。它是一个包含通知和切点的类。通知定义了在连接点处所要执行的程序代码以及执行时机,切点定义了切面将在哪些方法上插入。
(2)切点(Pointcut):切点是指一个表达式,用于描述横切关注点所应用的方法范围。切点决定了切面在哪些方法上被织入。
(3)通知(Advice):通知是在切点处执行的代码。它定义了在连接点处要执行的程序逻辑和执行时机。通知的类型包括前置通知、后置通知、环绕通知、异常通知和最终通知。
(4)连接点(Join Point):连接点是在应用程序执行过程中满足切点定义的具体位置。它可以是方法调用、方法执行、异常抛出等。切面将在连接点处织入,并在相应的时机执行通知。
(5)织入(Weaving):织入是将切面应用于目标对象的过程。它可以是编译时的静态织入,也可以是运行时的动态织入。编译时织入是在编译期间将切面代码编译到目标对象中。运行时织入是在程序运行期间动态地创建代理对象,将切面逻辑织入到目标对象中。
AOP体系梳理图:
4、通过xml的方式使用AOP
(1)在Spring配置额文件头部相应的地方加入xmlns:aop="http://www.springframework.org/schema/aop",在xsi:schemaLocation中加入http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
- 在Spring配置文件中加入AOP切面配置
<!-- AOP配置根节点 -->
<aop:config>
<!-- 定义切点 -->
<aop:pointcut expression="execution(* service.*.*(..))"
id="pointcut" />
<!-- 定义切面 -->
<aop:aspect ref="logService">
<!-- 定义前置通知 -->
<aop:before pointcut-ref="pointcut" method="writeLog" />
</aop:aspect>
</aop:config>
说明:
A.定义通知类bean
B.通过aop:config标签配置所有的切面
C.通过aop:pointcut标签配置切点,使用切点表达式来指定范围
- 通过aop:aspect定义切面,指定引用的通知以及通知的类型和切点
- 通知根据通知的时机区分有五种类型:
前置通知:使用aop:before标签,在方法调用前执行。前置通知可以使用@Before注解实现。
后置通知:使用aop:after标签,在方法调用后执行,无论方法内部是否抛出异常。后置通知可以使用@After注解实现。
后置返回通知:使用aop:after-returning标签,在方法调用后执行,并且方法内部不能抛出异常。后置返回通知可以使用@AfterReturning注解实现。
后置异常通知:使用aop:after-throwing标签,在方法内部抛出异常时执行。后置异常通知可以使用@AfterThrowing注解实现。
环绕通知:使用aop:around标签,在方法调用前后都执行。环绕通知是最为灵活的通知类型,可以完全自定义目标方法的调用、传参、返回值和异常处理。环绕通知可以使用@Around注解实现。
说明:
F.前四种通知的方法里面我们可以通过JoinPoint参数获取连接点信息,比如参数信息等,方便进行处理
G.环绕通知的方法里我们通过ProceedingJoinPoint参数来获取连接点信息
5、注解方式使用AOP
(1)在spring配置文件中开启aop注解方式
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 使用@Aspect定义切面,使用@Pointcut定义切点,使用@Before、@After、@Around、@AfterReturning、@AfterThrowing定义通知的应用时机。
- 在注解方式中,我们可以使用@Aspect注解将一个Java类标记为切面,并使用@Before/@After等注解在适当的位置声明要执行的通知代码。
以下是一个简单的注解示例:
@Component
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.UserService.addUser(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Adding user: " + Arrays.toString(joinPoint.getArgs()));
}
}
在这个示例中,我们创建了一个LoggingAspect类,并将其标记为@Aspect注解。我们使用@Before注解声明要在UserService类的addUser方法执行之前执行的代码。@Before("execution(* com.example.UserService.addUser(..))")表示切点表达式,说明我们要拦截执行UserService类的addUser方法。在logBefore方法中,我们将会输出添加用户的详细信息。
- Spring的事务管理详解
- 事物的特性:
- 原子性(Atomicity):事务被视为不可分割的最小操作单元,要么全部操作成功,要么全部失败回滚。
(2)一致性(Consistency):事务前后数据的状态应该是一致的,即满足业务规则的要求。如果一个事务执行失败,则数据回到事务开始前的状态,保持数据的一致性。
(3)隔离性(Isolation):多个事务并发执行时要相互隔离,每个事务之间互不影响。因此,在并发环境下,不同的事务之间应该是互相隔离的,这样才能保证每个事务执行的结果都是正确的,不会相互干扰。
(4)持久性(Durability):事务完成后,对系统的影响是永久的。事务处理完成后,对数据的修改被永久地保存到数据库中,即使系统崩溃也不会受到影响。
2、Spring事务管理概述
Spring事务管理的工作原理是利用了Spring框架中的一些事务相关的组件和注解,可以将代码中需要进行事务处理的代码块声明为一个事务,并使用Spring事务管理器来管理这些事务,这样就可以确保在整个事务过程中,所有的数据库访问操作都能被统一地纳入到事务中进行管理,从而保证整个事务的一致性和可靠性。
3、在 Spring 的事务模块(spring-tx-5.3.9.jar)中包括事务管理的三个核心接口。
(1)PlatformTransactionManager接口是Spring提供的平台事务管理器,主要用于管理事务。以下是此接口中提供的三个事物操作方法:
TransactionStatus getTransaction(TransactionDefinition definition);
用于获取事务以及状态信息
void commit(TransactionStatus status);
用于提交事务
void rollback(TransactionStatus status);
用于回滚事务
(2)PlatformTransactionManager接口只是代表事务管理的接口,并不知道底层是如何管理事务的,具体如何管理事务则由它的实现类来完成。
常用的接口实现类:
org.springframework.jdbc.datasource.DataSourceTransactionManager用于配置JDBC数据源的事务管理器;
org.springframework.orm.hibernate4.HibernateTransactionManager
用于配置Hibernate的事务管理器;
org.springframework.transaction.jta.JtaTransactionManager
用于配置全局事务管理器
(3)TransactionDefinition接口是事务定义(描述)的对象,该对象中定义了事务基本属性,并提供了获取事务基本属性的方法。
String getName( )获取事务对象名称;
int getIsolationLevel( )获取事务的隔离级别;
int getPropagationBehavior( )获取事务的传播行为;
int getTimeout( )获取事务的超时时间;
boolean isReadOnly( )获取事务是否只读;
4、事务的基本事务属性
A.传播行为(propagation behavior):当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。Spring定义了七种传播行为
B.隔离级别(isolation level):定义了一个事务可能受其他并发事务的影响程度。多个事务并发运行,经常会操作相同的数据来完成各自的任务,可能会出现脏读,不可重复读和幻读的问题。隔离级别有四种
C.是否只读(isReadOnly):如果一个方法内都是对数据库的select操作,那么可以设置方法事务为只读,数据库也会对该事务进行特定的优化。只读事务内不能有insert、update、delete的操作
D.事务超时(timeout):事务可能设计对后端数据库的锁定,所以长时间的事务运行会不必要的占用数据库资源,设置事务超时时间可以及时释放资源
5、TransactionDefinition
(1)TransactionDefinition接口是事务定义(描述)的对象,该对象中定义了事务基本属性,并提供了获取事务基本属性的方法,具体如下:
- String getName( ); (获取事务对象名称)
- int getIsolationLevel( );(获取事务的隔离级别)
- int getPropagationBehavior( );(获取事务的传播行为)
- int getTimeout( );(获取事务的超时时间)
- boolean isReadOnly( );(获取事务是否只读)
DefaultTransactionDefinition是Spring提供的TransactionDefinition接口的默认实现类,该类定义的事务规则如下:
public class DefaultTransactionDefinition implements TransactionDefinition, Serializable {
private int propagationBehavior = PROPAGATION_REQUIRED;
private int isolationLevel = ISOLATION_DEFAULT;
private int timeout = TIMEOUT_DEFAULT;
private boolean readOnly = false;
//略
}
也可以通过该类对象的set方法为事务设置其他的属性值。
6、TransactionStatus
TransactionStatus接口是事务的状态,它描述了某一时间点上事务的状态信息。该接口中包含6个方法,具体如下:
- void flush(); (刷新事务)
- boolean hasSavepoint();(获取是否存在保存点)
- boolean isCompleted(); (获取事务是否完成)
- boolean isNewTransaction();(获取是否为新事务)
- boolean isRollbackOnly(); (获取事务是否回滚)
- void setRollbackOnly();(设置事务回滚)
7、事物管理方式
- 添加事务管理器组件
在applicationContext.xml中配置一个事务管理器组件,提供对事务处理的全面支持和统一管理
<!--定义事务管理器 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
- 在业务逻辑类中使用事务管理器
@Autowired
private PlatformTransactionManager txManager;//通过依赖注入使用事务管理器
- 在方法中编程实现事务管理
//定义事务规则(隔离级别、传播行为)
DefaultTransactionDefinition definition=new DefaultTransactionDefinition();
//开启事务管理,并返回事务状态
TransactionStatus status = txManager.getTransaction(definition);
try {
//转账业务逻辑(省略)
txManager.commit(status); //提交事务
} catch (Exception e) {
txManager.rollback(status); //如果出现异常回滚事务
}
- 编程式事务管理缺点
编程式事务管理必须要在业务逻辑中包含额外的事务管理代码。和业务逻辑代码产生了耦合,产生了代码冗余,不方便代码的维护和扩展。
- 编程式事务管理优点
声明式事务管理最大的优点在于开发者无需通过编程的方式来管理事务,只需在配置文件中进行相关的事务规则声明,就可以将事务应用到业务逻辑中。这使得开发人员可以更加专注于核心业务逻辑代码的编写,在一定程度上减少了工作量,提高了开发效率,所以在实际开发中,通常都推荐使用声明式事务管理。
- 声明式事务管理
声明式事务管理的原理
通过AOP技术实现的事务管理,主要思想是将事务作为一个“切面”代码单独编写,然后通过AOP技术将事务管理的“切面”植入到业务目标类中
如何实现Spring的声明式事务管理
Spring的声明式事务管理可以通过两种方式来实现,一种是基于XML的方式,另一种是基于注解的方式。
- 基于XML方式的声明式事务
基于XML方式的声明式事务是在配置文件中通过<tx:advice>元素配置事务规则来实现的,然后通过使用<aop:config>编写的AOP配置,让Spring自动对目标生成代理。<tx:advice>元素及其子元素如下图所示:
配置<tx:advice>元素的重点是配置<tx:method>子元素,上图中使用灰色标注的几个属性是<tx:method>元素中的常用属性。其属性描述具体如下:
- 添加事务管理器组件:
//在XML配置文件中添加一个事务管理器组件,用于管理事务操作:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
//这里使用了DataSourceTransactionManager来管理事务,确保根据需要将dataSource设置为适当的数据源。
- 使用 <tx:advice> 标签配置事务规则
//因为要用到tx标签配置事务,需要修改applicationContext.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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
//通过 <tx:advice> 标签配置事务规则,定义需要进行事务管理的方法名、事务//传播属性等:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
//tx:method 元素定义了一组方法名的模式(例如 name="save*"),以及他们的//事务传播属性(例如 propagation="REQUIRED")。可以根据实际需求进行自定//义配置。
- 使用 <aop:config> 配置事务切面:
//使用 <aop:config> 元素配置事务切面,并将事务规则和切入点关联起来:
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.example.service..*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
</aop:config>
//<aop:pointcut> 定义了一个切入点,expression 属性指定了切入的方法表达式。//<aop:advisor> 元素将事务规则 txAdvice 和切入点 txPointcut 关联起来,确保//切入点匹配的方法被应用事务规则。
- 基于注解方式的声明式事务
1)配置事务管理器
在applicationContext.xml中配置一个事务管理器组件,提供对事务处理的全面支持和统一管理
<!--定义事务管理器 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
2)注册事务注解驱动
在applicationContext.xml中添加对注解配置事务的支持
<tx:annotation-driven transaction-manager=“txManager"/>
3)注册事务注解驱动
在需要事务管理的类或方法上使用@Transactional注解
例如:
@Service
public class MyService {
@Autowired
private MyDao myDao;
@Transactional
public void saveData(Data data) {
// 保存数据的逻辑
myDao.save(data);
}
@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
public Data getDataById(Long id) {
// 获取数据的逻辑
return myDao.getById(id);
}
}
/*@Transactional注解被添加到了saveData()方法和getDataById()方法上。你可以根据实际需求设置propagation(事务传播属性)和readOnly(是否为只读事务)等属性。*/
注意:如果将注解添加在Bean类中的某个方法上,则表示事务的设置只对该方法有效。
使用@Transactional注解时,可以通过参数配置事务详情: