SpringAOP入门基础银行转账实例**(进阶版)**------------事务处理
由上一节讲述的通过Connection和QueryRunner对事务进行的处理(详情可以去我之前写的博客文章:https://blog.csdn.net/m0_56245143/article/details/130069160?spm=1001.2014.3001.5501查看)
接下来由我们将对它进行简单的AOP改造
我们还是沿用上次的项目模块:
Spring基于配置文件的AOP
环境搭建
对该项目进行maven工程添加依赖:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>7</source>
<target>7</target>
</configuration>
</plugin>
</plugins>
</build>
<groupId>com.etime</groupId>
<artifactId>day05</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
<!--导入spring的context坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--导入Jdbc模块依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- DBUtils -->
<!-- <dependency>-->
<!-- <groupId>commons-dbutils</groupId>-->
<!-- <artifactId>commons-dbutils</artifactId>-->
<!-- <version>1.6</version>-->
<!-- </dependency>-->
<!-- 数据库相关 -->
<!-- <dependency>-->
<!-- <groupId>mysql</groupId>-->
<!-- <artifactId>mysql-connector-java</artifactId>-->
<!-- <version>5.1.6</version>-->
<!-- </dependency>-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<!--c3p0-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- 添加测试依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- dbutils依赖的添加-->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<!-- 添加aop配置依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
</project>
创建Spring的配置文件并导入约束
准备的资源:需要扫描当前项目包下的com.etime、需要配置文件内的连接数据库的基本资源、以及导入数据库连接的约束、配置JdbcTemplate模块
application.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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 扫描包-->
<context:component-scan base-package="com.etime"></context:component-scan>
<!--加载属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--数据源对象-->
<bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置JdbcTemplate模板对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="ds"/>
</bean>
<!-- 加载QueryRunner-->
<bean id="qr" class="org.apache.commons.dbutils.QueryRunner"></bean>
<!-- 配置切面-->
<!-- <aop:config>-->
<!-- <aop:aspect id="tm" ref="transactionUtil">-->
<!--<!– 配置切面点–>-->
<!--<!– 配置com.etime.service.impl包下的AccountServiceImpl类中的transferAccount方法–>-->
<!-- <aop:pointcut id="po" expression="execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))"/>-->
<!--<!– 开始事务–>-->
<!--<!– <aop:before method="startTransaction" pointcut-ref="po"></aop:before>–>-->
<!--<!–<!– 提交事务–>–>-->
<!--<!– <aop:after-returning method="commitTransaction" pointcut-ref="po"></aop:after-returning>–>-->
<!--<!–<!– 出现错误回滚–>–>-->
<!--<!– <aop:after-throwing method="rollBackTransaction" pointcut-ref="po"></aop:after-throwing>–>-->
<!--<!–<!– 关闭事务–>–>-->
<!--<!– <aop:after method="closeTransaction" pointcut-ref="po"></aop:after>–>-->
<!-- <aop:around method="transactionAround" pointcut-ref="po"></aop:around>-->
<!-- </aop:aspect>-->
<!-- </aop:config>-->
<!--<!– 开启spring对注解AOP的支持–>-->
<!--<!– <aop:aspectj-autoproxy/>–>-->
<!--<!– 配置事务管理器–>-->
<!-- <bean id="dtm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">-->
<!-- <property name="dataSource" ref="ds"></property>-->
<!-- </bean>-->
<!--<!– 配置事务的通知引用事务管理器–>-->
<!--<!– 在tx:advice中配置事务的属性–>-->
<!-- <tx:advice id="d" transaction-manager="dtm">-->
<!-- <tx:attributes>-->
<!-- <tx:method name="*"/>-->
<!-- </tx:attributes>-->
<!-- </tx:advice>-->
<!--<!– 配置AOP切入点表达式–>-->
<!-- <aop:config>-->
<!-- <aop:pointcut id="point" expression="execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))"/>-->
<!--<!– 在aop:config标签内部:建立事务通知和切入点表达式的关系–>-->
<!-- <aop:advisor advice-ref="d" pointcut-ref="point"></aop:advisor>-->
<!-- </aop:config>-->
</beans>
创建ConnectionUtil.java完成连接的反转控制
ConnectionUtil.java
package com.etime.util;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import sun.rmi.transport.Connection;
@Component("connection")
public class ConnectionUtil {
// @Autowired
// private JdbcTemplate jdbcTemplate;
@Autowired
private ComboPooledDataSource ds;
@Bean(name = "connection")
public Connection getConnection() throws Exception {
return (Connection) ds.getConnection();
}
}
AOP配置
使用aop:config声明AOP配置
aop:config:
作用:开始声明aop配置
< aop:config >
配置的代码
< /aop:config >
使用aop:aspect配置切面
aop: aspect
作用:用于配置切面
属性:
id:给切面提供一个唯一标识
ref:引用配置好的通知类bean的id
<aop:aspect id="tm" ref="transactionUtil">
...
< /aop:aspect>
使用aop:pointcut配置切入点表达式
aop:pointcut
作用:用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
属性:expression:用于定义切入点表达式。
id:用于切入点表达式提供一个唯一标识
< aop : poincut id="po" expression="execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))"/>
使用aop:xx配置对应的通知类型
aop:before
作用:用于配置前置通知。指定增强的方法在切入点方法之前执行
属性:
method:用于指定通知类中的增强方法名称
ponitcut-ref:用于指定切入点的表达式的引用
ponitcut:用于指定切入点表达式
执行时间点:
切入点方法执行之前执行
<aop:before method="startTransaction" pointcut-ref="po" ></aop:before >
aop:after-returning
作用:
用于配置后置通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
切入点方法正常执行之后。它和异常通知只能有一个执行
<aop:after-returning method="commitTransaction" pointcut-ref="po"></aop:after-returning >
aop:after-throwing
作用:
用于配置异常通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
切入点方法执行产生异常后执行。它和后置通知只能执行一个
<aop:after-throwing method="rollBackTransaction" pointcut-ref="po"></aop:after-throwing >
aop:after
作用:
用于配置最终通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:
无论切入点方法执行时是否有异常,它都会在其后面执行。
<aop:after method="closeTransaction" pointcut-ref="po"></aop:after >
application.xml
配置后的代码:
<!-- 配置切面-->
<aop:config>
<aop:aspect id="tm" ref="transactionUtil">
<!-- 配置切面点-->
<!-- 配置com.etime.service.impl包下的AccountServiceImpl类中的transferAccount方法-->
<aop:pointcut id="po" expression="execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))"/>
<!-- 开始事务-->
<aop:before method="startTransaction" pointcut-ref="po"></aop:before>
<!-- 提交事务-->
<aop:after-returning method="commitTransaction" pointcut-ref="po"></aop:after-returning>
<!-- 出现错误回滚-->
<aop:after-throwing method="rollBackTransaction" pointcut-ref="po"></aop:after-throwing>
<!-- 关闭事务-->
<aop:after method="closeTransaction" pointcut-ref="po"></aop:after>
<!-- <aop:around method="transactionAround" pointcut-ref="po"></aop:around>-->
</aop:aspect>
</aop:config>
service:修改
package com.etime.service.impl;
import com.etime.dao.AccountDao;
import com.etime.entity.Account;
import com.etime.service.AccountService;
import com.etime.util.TransactionUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.sql.SQLException;
@Service("as")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
// @Autowired
// private TransactionUtil transactionUtil;
//这里不能将此处的异常try,catch.spring只能捕捉throws的异常
@Override
public void transferAccount(String name1, String name2, double money) throws SQLException {
// try {
// //开启事务
// transactionUtil.startTransaction();
//收到的钱
Account accountOne=accountDao.getByName(name1);
accountOne.setMoney(accountOne.getMoney()+money);
accountDao.updateAccount(accountOne);
//钱转出
Account accountTwo=accountDao.getByName(name2);
accountTwo.setMoney(accountTwo.getMoney()-money);
accountDao.updateAccount(accountTwo);
// //以上数据没有数据操作错误,就提交
// transactionUtil.commitTransaction();
// }catch (SQLException e){
// //如果数据有误,进行数据回滚
// transactionUtil.rollBackTransaction();
// e.printStackTrace();
// }finally {
//如果服务结束,事务关闭(不管是否服务成功都进行最后的事务关闭)
// transactionUtil.closeTransaction();
// }
}
}
如图所示的运行结果:异常事务能正常处理
切点表达式说明
切点表达式的语法
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号* 代表任意
- 包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类
- 参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表
例如:
全匹配方式
public void
com.etime.service.impl.AccountServiceImpl.saveAccount(com.etime.domain.Account)
访问修饰符可以省略
void com.etime.service.impl.AccountServiceImpl.saveAccount(com.etime.domain.Account)
返回值可以使用*号,表示任意返回值
* com.etime.service.impl.AccountServiceImpl.saveAccount(com.etime.domain.Account)
包名可以使用 * 号,表示任意包,但是有几级包,需要写几个 *
* *.*.*.*.AccountServiceImpl.saveAccount(com.etime.domain.Account)
使用…来表示当前包,及其子包
* com..AccountServiceImpl.saveAccount(com.etime.domain.Account)
类名可以使用*号,表示任意类
* com..*.saveAccount(com.etime.domain.Account)
方法名可以使用*号,表示任意方法
* com..*.*(com.etime.domain.Account)
参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
* com..*.*(*)
参数列表可以使用…表示有无参数均可,有参数可以是任意类型
* com..*.*(..)
全通配方式:
* *..*.*(..)
注意: 通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
execution(* com.etime.service.impl.*.*(..))
环绕通知配置事务管理
在TransactionUtil类当中添加方法
/**
* 环绕通知:
* spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。
* 在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。
* @param pjp
* @return
*/
//环绕通知方法
public Object transactionAround(ProceedingJoinPoint pjp) {
Object result = null;
try {
//获取调用切入点方法时传入的参数
Object[] args = pjp.getArgs();
startTransaction();
//运行切入点方法
result = pjp.proceed(args);
commitTransaction();
} catch (Throwable throwable) {
rollbackTransaction();
throwable.printStackTrace();
} finally {
closeConnection();
}
return result;
}
aop:around:
作用:
用于配置环绕通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
说明:
它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
注意:通常情况下,环绕通知都是独立使用的
<!-- 配置切面-->
<aop:config>
<aop:aspect id="tm" ref="transactionUtil">
<!-- 配置切面点-->
<!-- 配置com.etime.service.impl包下的AccountServiceImpl类中的transferAccount方法-->
<aop:pointcut id="po" expression="execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))"/>
<!-- 开始事务-->
<!-- <aop:before method="startTransaction" pointcut-ref="po"></aop:before>-->
<!--<!– 提交事务–>-->
<!-- <aop:after-returning method="commitTransaction" pointcut-ref="po"></aop:after-returning>-->
<!--<!– 出现错误回滚–>-->
<!-- <aop:after-throwing method="rollBackTransaction" pointcut-ref="po"></aop:after-throwing>-->
<!--<!– 关闭事务–>-->
<!-- <aop:after method="closeTransaction" pointcut-ref="po"></aop:after>-->
<aop:around method="transactionAround" pointcut-ref="po"></aop:around>
</aop:aspect>
</aop:config>
运行结果:正常异常事务处理
Spring基于注解的AOP
AOP注解方式和XML方式完成的功能都是一样的,只是采用了两种开发方式而已。将原有的XML方式使用注解逐一替代。
接上一节所配置的环境
通知类使用注解配置和使用@Acpect注解声明为切面
在增强的方法上使用注解配置通知
@Before
作用:
把当前方法看成是前置通知
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@Before("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
public void startTransaction() {
try {
System.out.println("启动事务");
connection.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
@AfterReturning
作用:
把当前方法看成是后置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
@AfterReturning("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
public void commitTransaction() {
try {
System.out.println("提交");
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
@AfterThrowing
作用:
把当前方法看成是异常通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
@AfterThrowing("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
public void rollBackTransaction() {
try {
System.out.println("回滚");
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
@After
作用:
把当前方法看成是最终通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用
@After("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
public void closeTransaction() {
try {
System.out.println("释放资源");
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
到这里由上述的配置后可以直接运行,也是同样对事务进行处理,这里我就不运行了,只是用注解的方式更加的简洁
环绕通知配置事务管理
在TransactionUtil类中添加环绕配置
@Around
作用:
把当前方法看成是环绕通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@Around("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
public Object transactionAround(ProceedingJoinPoint pjp) {
Object result = null;
try {
Object[] args = pjp.getArgs();
startTransaction();
result = pjp.proceed(args);
commitTransaction();
} catch (Throwable throwable) {
rollbackTransaction();
throwable.printStackTrace();
} finally {
closeConnection();
}
return result;
}
加环绕配置
@Around
作用:
把当前方法看成是环绕通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
@Around("execution(public void com.etime.service.impl.AccountServiceImpl.transferAccount(..))")
public Object transactionAround(ProceedingJoinPoint pjp) {
Object result = null;
try {
Object[] args = pjp.getArgs();
startTransaction();
result = pjp.proceed(args);
commitTransaction();
} catch (Throwable throwable) {
rollbackTransaction();
throwable.printStackTrace();
} finally {
closeConnection();
}
return result;
}