AOP事务管理的原理与及三种实现方式

本文围绕Spring AOP事务管理展开,介绍了AOP的定义、基本概念,如连接点、切入点等。阐述了AOP的作用和特点,还讲解了AOP事务管理的实现方式,包括XML实现和注解实现,推荐使用spring - tx,并介绍了事务传播行为。

AOP事务管理

AOP的定义

spect Oriented Programing 面向切面编程,相比较 oop 面向对象编程来说,Aop关注的不再是程序代码中某个类,某些方法,而aop考虑的更多的是一种面到面的切入,即层与层之间的一种切入,所以称之为切面。联想大家吃的汉堡(中间夹肉)。那么aop是怎么做到拦截整个面的功能呢?考虑前面学到的servlet filter /* 的配置,实际上也是aop 的实现。

作用:

AOP主要应用于日志记录,性能统计,安全控制,事务处理等方面,实现公共功能性的重复使用。

特点:

  1. 降低模块与模块之间的耦合度,提高业务代码的聚合度。(高内聚低耦合)
  2. 提高了代码的复用性。
  3. 提高系统的扩展性。(高版本兼容低版本)
  4. 可以在不影响原有的功能基础上添加新的功能

AOP的基本概念

Joinpoint(连接点)

被拦截到的每个点,spring中指被拦截到的每一个方法,spring aop一个连接点即代表一个方法的执行。

Pointcut(切入点)

对连接点进行拦截的定义(匹配规则定义规定拦截哪些方法,对哪些方法进行处理),spring 有专⻔的表达式语言定义。

Advice(通知)

拦截到每一个连接点即(每一个方法)后所要做的操作

  • 前置通知(前置增强)— before() 执行方法前通知
  • 返回通知(返回增强)— afterReturn 方法正常结束返回后的通知
  • 异常抛出通知(异常抛出增强)— afetrThrow()
  • 最终通知 — after 无论方法是否发生异常,均会执行该通知。
  • 环绕通知 — around 包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

Aspect(切面)

切入点与通知的结合,决定了切面的定义,切入点定义了要拦截哪些类的哪些方法,通知则定义了拦截过方法后要做什么,切面则是横切关注点的抽象,与类相似,类是对物体特征的抽象,切面则是横切关注点抽象。

Target(目标对象)

被代理的目标对象

Weave(织入)

将切面应用到目标对象并生成代理对象的这个过程即为织入

Introduction(引入)

在不修改原有应用程序代码的情况下,在程序运行期为类动态添加方法或者字段的过程称为引入

个人愚解

在这里插入图片描述

切面应该在service层!!!

实现方式XML实现

内部事务:用xml实现(导入依赖中的事务)

手动事务:注解实现(自己写的,项目中的事务)

/**
*切面
*切入点和通知的抽象(与面向对象中的类相似)
*定义切入点和通知(切入点定义了要拦截哪些类的哪些方法,通知则定义了拦截过方法后要做什么)
*/
@Component// 将对象交给IOC容器去实例化
public class LogCut02 {
    public void cut(){
        
    }
/**
* 声明前置通知并将通知应用到定义的切入点上
* 目标类方法执行前执行该通知
*
*/
    public void before() {
        System.out.println("前置通知.....");   
    }
/**
* 声明返回通知并将通知应用到定义的切入点上
* 目标类方法(无异常)执行后执行该通知
*
*/
    public void afterReturn() {
		System.out.println("返回通知.....");    
    }
/**
* 声明最终通知并将通知应用到定义的切入点上
* 目标类方法(无异常或有异常)执行后执行该通知
*
*/
    public void after() {
        System.out.println("最终通知.....");    
    }
/**
* 声明异常通知并将通知应用到定义的切入点上
* 目标类方法出现异常时执行该通知
*/
    public void afterThrow(Exception e) {
        System.out.println("异常通知....."+"  异常原因:"+e.getCause());    
    }
/**
*  声明环绕通知并将通知应用到切入点上
*  方法执行前后通过环绕通知定义相应处理
*      需要通过显式调用对应的方法,否则无法访问指定方法 (pjp.proceed();)
* @param pjp
* @return
*/
    public Object around(ProceedingJoinPoint pjp) {
        System.out.println("前置通知...");
        Object object=null;
        try {object=pjp.proceed();
             System.out.println(pjp.getTarget() +"======"+pjp.getSignature());
             // System.out.println("返回通知...");        
            } catch (Throwablethrowable) {
            throwable.printStackTrace();
            System.out.println("异常通知...");     
        }
        System.out.println("最终通知...");
        return object;    
    }

beans.xml添加的配置

xmlns:aop="http://www.springframework.org/schema/aop"

http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd

....
<!--aop相关配置-->
<aop:config>
    <!--aop切面  切面实现类-->
    <aop:aspectref="logCut02"> 
        <!-- 定义aop 切入点 -->
        <aop:pointcutid="cut"expression="execution(* com.xxxx.service..*.*(..))"/>
        <!-- 配置前置通知指定前置通知方法名并引用切入点定义 -->
        <aop:beforemethod="before"pointcut-ref="cut"/>
        <!-- 配置返回通知指定返回通知方法名并引用切入点定义 -->
        <aop:after-returningmethod="afterReturn"pointcut-ref="cut"/>
        <!-- 配置异常通知指定异常通知方法名并引用切入点定义 -->
        <aop:after-throwingmethod="afterThrow"throwing="e"pointcut-ref="cut"/>
        <!-- 配置最终通知指定最终通知方法名并引用切入点定义 -->
        <aop:aftermethod="after"pointcut-ref="cut"/>
        <!-- 配置环绕通知指定环绕通知方法名并引用切入点定义 -->
        <aop:aroundmethod="around"pointcut-ref="cut"/>
        </aop:aspect>
</aop:config>

注解实现 org.aspectj

坐标依赖引入

<!--Spring AOP-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.9</version>
</dependency>

添加spring.xml的配置

添加命名空间 配置AOP代理

xmlns:aop="http://www.springframework.org/schema/aop"

http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
....
<!--配置AOP代理-->
<aop:aspectj-autoproxy/>

注解实现类

/**
* 切面*  切入点和通知的抽象(与面向对象中的类相似)
* 定义切入点和通知(切入点定义了要拦截哪些类的哪些方法,通知则定义了拦截过方法后要做什么)
*/
@Component// 将对象交给IOC容器去实例化
@Aspect// 声明当前类是一个切面
public class LogCut {
    /**
    *  切入点:
    *      匹配规则。规定什么方法被拦截、需要处理什么方法
    *  定义切入点
    *    @Pointcut("匹配规则")
    *
    *    Aop 切入点表达式简介
    *       1. 执行任意公共方法:
    *       execution(public *(..))
    *       2. 执行任意的set方法*       execution(* set*(..))
    * 3. 执行com.xxxx.service包下任意类的任意方法
    *      execution(* com.xxxx.service.*.*(..))
    *       4. 执行com.xxxx.service 包以及子包下任意类的任意方法
    *     execution(* com.xxxx.service..*.*(..))
    *
    *    注:表达式中的第一个* 代表的是方法的修饰范围
    *	 可选值:private、protected、public (* 表示所有范围)*/
    @Pointcut("execution (* com.xxxx.service..*.*(..) )")
    public void cut(){
        
    }
    /**
    * 声明前置通知并将通知应用到定义的切入点上
    * 目标类方法执行前执行该通知
    *
    */
    @Before(value="cut()")
    public void before() {
        System.out.println("前置通知.....");   
    }
    /**
    * 声明返回通知并将通知应用到定义的切入点上
    * 目标类方法(无异常)执行后执行该通知
    *
    */
    @AfterReturning(value="cut()")
    public void afterReturn() {
        System.out.println("返回通知.....");    
    }
    /**
    * 声明最终通知并将通知应用到定义的切入点上
    * 目标类方法(无异常或有异常)执行后执行该通知
    *
    */
    @After(value="cut()")
    public void after() { 
        System.out.println("最终通知.....");    
    }
    /**
    * 声明异常通知并将通知应用到定义的切入点上
    * 目标类方法出现异常时执行该通知
    */
    @AfterThrowing(value="cut()",throwing="e")
    public void afterThrow(Exceptione) {
        System.out.println("异常通知....."+"  异常原因:"+e.getCause());    
    }
    /**
    *  声明环绕通知并将通知应用到切入点上   与上面四个方法等价需要注释之前的方法
    *  方法执行前后通过环绕通知定义相应处理
    *      需要通过显式调用对应的方法,否则无法访问指定方法 (pjp.proceed();)
    * @param pjp* @return
    */
    @Around(value="cut()") 
    public Object around(ProceedingJoinPointpjp) {
        System.out.println("前置通知...");
        Objectobject=null;
        try {
            object=pjp.proceed();
            System.out.println(pjp.getTarget() +"======"+pjp.getSignature());// 
            System.out.println("返回通知...");       
        } catch (Throwablethrowable) {
            throwable.printStackTrace();
            System.out.println("异常通知..."); 
        }
    	 System.out.println("最终通知...");
         return object;   
    }
}   

建议推荐使用 spring-tx

maven项目导入依赖

<!-- spring事物 事务处理 -->
<dependency>
 	<groupId>org.springframework</groupId>
 	<artifactId>spring-tx</artifactId>
 	<version>5.2.4.RELEASE</version>
</dependency>
<!-- aop -->
<dependency>
 	<groupId>org.aspectj</groupId>
 	<artifactId>aspectjweaver</artifactId>
 	<version>1.9.5</version>
</dependency>

xml配置

在spring.xml配置⽂件的添加事务和aop的命名空间

事务。。
xmlns:tx="http://www.springframework.org/schema/tx"

http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd

AOP。。。
xmlns:aop="http://www.springframework.org/schema/aop"

http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd

开启AOP代理

<!-- 开启AOP代理 -->
<aop:aspectj-autoproxy />

配置事务管理器

<!-- 事务管理器定义 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 <!--数据源 -->
 <property name="dataSource" ref="dataSource"></property>
</bean>

配置相关的事务通知

<!-- 配置事务通知 transaction-manager属性表示这个事务通知是哪个事务管理器管理的-->
<!--
 tx:method的属性:
 name
 是必须的,表示与事务属性关联的⽅法名(业务⽅法名),对切⼊点进⾏细化。
 通配符(*)可以⽤来指定⼀批关联到相同的事务属性的⽅法。
 如:'get*'、'handle*'、'on*Event'等等.
 propagation
 不是必须的,默认值是REQUIRED
 表示事务传播⾏为, 包括: 
 REQUIRED,SUPPORTS,MANDATORY,NEVER
 REQUIRES_NEW,NOT_SUPPORTED,NESTED
 isolation 
 不是必须的,默认值DEFAULT
 表示事务隔离级别(数据库的隔离级别)
 timeout
不是必须的,默认值-1(永不超时)
 表示事务超时的时间(以秒为单位)
 read-only
 不是必须的,默认值false不是只读的
 表示事务是否只读
 rollback-for
 不是必须的
 表示将被触发进⾏回滚的 Exception(s);以逗号分开。
 如:'com.foo.MyBusinessException,ServletException'
 no-rollback-for
 不是必须的
 表示不被触发进⾏回滚的 Exception(s);以逗号分开。
 如:'com.foo.MyBusinessException,ServletException'
 任何 RuntimeException 将触发事务回滚
 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
 <!--对以add update delete query开头的所有⽅法进⾏事务处理-->
 <tx:attributes>
 <!--定义什么⽅法需要使⽤事务 name代表的是⽅法名(或⽅法匹配)-->
 <!-- 匹配以 add 开头的所有⽅法均加⼊事务 -->
 <tx:method name="add*" propagation="REQUIRED" />
 <!-- 匹配以 update 开头的所有⽅法均加⼊事务 -->
 <tx:method name="update*" propagation="REQUIRED" />
 <!-- 匹配以 delete 开头的所有⽅法均加⼊事务 -->
 <tx:method name="delete*" propagation="REQUIRED" />
 <!-- 匹配以 query 开头的所有⽅法均加⼊事务 -->
 <tx:method name="query*" read-only="true" />
 </tx:attributes>
</tx:advice>

事务传播⾏为介绍:

@Transactional(propagation=Propagation.REQUIRED)

如果有事务, 那么加⼊事务, 没有的话新建⼀个(默认情况下)

@Transactional(propagation=Propagation.NOT_SUPPORTED)

容器不为这个⽅法开启事务

@Transactional(propagation=Propagation.REQUIRES_NEW)

不管是否存在事务,都创建⼀个新的事务,原来的挂起,新的执⾏完毕,继续执⾏⽼的事务

@Transactional(propagation=Propagation.MANDATORY)

必须在⼀个已有的事务中执⾏,否则抛出异常

@Transactional(propagation=Propagation.NEVER)

必须在⼀个没有的事务中执⾏,否则抛出异常(与 Propagation.MANDATORY 相反)

@Transactional(propagation=Propagation.SUPPORTS)

如果其他 bean 调⽤这个⽅法,在其他 bean 中声明事务,那就⽤事务.

如果其他 bean 没有声明事务,那就不⽤事务.

@Transactional(propagation=Propagation.NESTED)

⽀持当前事务,如果当前事务存在,则执⾏⼀个嵌套事务,如果当前没有事务,就新建⼀个事务。

配置AOP

<!-- 设置切面,定义切入点和通知 -->
    <aop:config>
        <!-- 切入点:定义哪些方法需要被拦截 -->
        <aop:pointcut id="cut" expression="execution(* com.shsxt.service..*(..))"/>
        <!-- 事务通知:拦截之后需要做什么操作 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="cut"/>
    </aop:config>

配置后的模板

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 开启自动扫描,设置扫描包范围 -->
    <context:component-scan base-package="com.shsxt"/>

    <!-- 加载properties 配置文件,用来读取jdbc.properties文件中的数据 -->
    <context:property-placeholder location="jdbc.properties" />

    <!-- 配置 c3p0 数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- property标签的value属性对应的是jdbc.properties中的值 -->
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="user" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!-- 配置JdbcTemplate实例,并注入一个dataSource数据源-->
    <bean id="jdbcTemplate"  class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    
    <!--
        Spring事务配置
            1. 添加tx和aop的命名空间
            2. 开启AOP自动代理
            3. 设置事务管理器,并绑定数据源
            4. 设置事务通知,并绑定事务管理器
            5. 设置切面,定义切入点和通知
    -->
    
    <!-- 开启AOP自动代理 -->
    <aop:aspectj-autoproxy/>
    
    <!-- 设置事务管理器,并绑定数据源 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 设置数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 设置事务通知,并绑定事务管理器 -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <!--定义哪些方法需要事务管理  name属性表示方法名,可以使用通配符。  以add开头的所有方法,加入事务 -->
            <tx:method name="add*" propagation="REQUIRED" />
            <!--定义哪些方法需要事务管理  name属性表示方法名,可以使用通配符。  以update开头的所有方法,加入事务 -->
            <tx:method name="update*" propagation="REQUIRED" />
            <!--定义哪些方法需要事务管理  name属性表示方法名,可以使用通配符。  以delete开头的所有方法,加入事务 -->
            <tx:method name="delete*" propagation="REQUIRED" />
            <!-- 设置查询操作的事务为只读 -->
            <tx:method name="query*" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    
    <!-- 设置切面,定义切入点和通知 -->
    <aop:config>
        <!-- 切入点:定义哪些方法需要被拦截 -->
        <aop:pointcut id="cut" expression="execution(* com.ppl.service..*(..))"/>
        <!-- 事务通知:拦截之后需要做什么操作 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="cut"/>
    </aop:config>
</beans>

注解实现

也可使用注解,将事务配置替换,或者可以不更改。但要确定命名方法不在上面的扫描中

<!--
        Spring事务配置
            1. 添加tx和aop的命名空间
            2. 开启AOP自动代理
    -->
    
    <!-- 开启AOP自动代理 -->
    <aop:aspectj-autoproxy/>
    
    <!-- 设置事务管理器,并绑定数据源 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 设置数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 设置注解支持  spring 注解式事务声明 -->
    <tx:annotation-driven transaction-manager="txManager"/>

只需要在类上:@Transactional(propagation = Propagation.REQUIRED)

@Service
public class AccountService {
    @Resource
    private AccountDaoImpl accountDao;
    /**
     * 事务的使用
     * @param outId
     * @param money
     * @param inId
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void updateTranfer(Integer outId, Double money, Integer inId) {
        // 支出操作
        Integer row = accountDao.outMoney(outId, money);
        int i = 1/0;
        // 收入操作
        Integer row2 = accountDao.inMoney(inId, money);
        // 判断是否操作成功
        if (row == 1 && row2 == 1) {
            System.out.println("转账成功!");
        } else {
            System.out.println("转账失败 !");
        }
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值