Spring的深入浅出(6)--使用AOP的思想改造转账案例

在Spring中使用AOP(基于注解)


使用AOP的思想改造转账案例

业务类改造:移除事务管理代码

public class AccountServiceImpl implements AccountService {

private AccountDao accountDao;

public void setAccountDao(AccountDao accountDao) {

this.accountDao = accountDao;

}

// 转账业务逻辑(纯业务代码)

public void transfer(String source, String target, Double money) {

Account sourceAccount = accountDao.findAccountByName(source);

Account targetAccount = accountDao.findAccountByName(target);

if (sourceAccount != null && targetAccount != null) {

sourceAccount.setMoney(sourceAccount.getMoney() - money);

accountDao.updateAccount(sourceAccount);

// 模拟异常(测试事务回滚)

int num = 100 / 0;

targetAccount.setMoney(targetAccount.getMoney() + money);

accountDao.updateAccount(targetAccount);

}

}

}

优点:

  1. 移除了所有事务管理相关的代码(begin/commit/rollback)
  2. 业务逻辑更纯净,只关注核心转账功能
  3. 保留了模拟异常的代码用于测试事务回滚
  4. 符合单一职责原则,职责分离更清晰

事务管理器(切面类)

public class TransactionManager {

private ConnectionUtils connectionUtils;

public void setConnectionUtils(ConnectionUtils connectionUtils) {

this.connectionUtils = connectionUtils;

}

// 开启事务

public void beginTransaction() {

try {

Connection connection = connectionUtils.getConnection();

connection.setAutoCommit(false);

} catch (SQLException e) {

throw new RuntimeException("开启事务失败", e);

}

}

// 提交事务

public void commitTransaction() {

try {

connectionUtils.getConnection().commit();

} catch (SQLException e) {

throw new RuntimeException("提交事务失败", e);

}

}

// 回滚事务

public void rollBackTransaction() {

try {

connectionUtils.getConnection().rollback();

} catch (SQLException e) {

throw new RuntimeException("回滚事务失败", e);

}

}

// 关闭连接(带资源清理)

public void closeConnection() {

try {

Connection conn = connectionUtils.getConnection();

conn.setAutoCommit(true); // 恢复自动提交

conn.close(); // 实际应归还连接池

connectionUtils.removeConnection(); // 清除ThreadLocal

} catch (SQLException e) {

throw new RuntimeException("关闭连接失败", e);

}

}

}

优点:

  1. 完整的事务管理功能封装(开启/提交/回滚/关闭)
  2. 使用ConnectionUtils确保线程安全的连接管理
  3. closeConnection()中恢复自动提交状态是良好实践
  4. 异常处理升级为运行时异常,符合Spring事务管理规范

Spring AOP配置(XML方式)

<!-- 1. 引入AOP命名空间 -->

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

<!-- 2. 定义事务管理器Bean -->

<bean id="transactionManager" class="com.xq.utils.TransactionManager">

<property name="connectionUtils" ref="connectionUtils"/>

</bean>

<!-- 3. 配置AOP -->

<aop:config>

<!-- 定义切点:AccountServiceImpl的transfer方法 -->

<aop:pointcut id="txPointcut"

expression="execution(* com.xq.service.impl.AccountServiceImpl.transfer(..))"/>

<!-- 配置切面 -->

<aop:aspect ref="transactionManager" order="1">

<!-- 前置通知:开启事务 -->

<aop:before method="beginTransaction" pointcut-ref="txPointcut"/>

<!-- 返回通知:提交事务 -->

<aop:after-returning method="commitTransaction" pointcut-ref="txPointcut"/>

<!-- 异常通知:回滚事务 -->

<aop:after-throwing method="rollBackTransaction"

pointcut-ref="txPointcut"

throwing="ex"/>

<!-- 最终通知:关闭连接 -->

<aop:after method="closeConnection" pointcut-ref="txPointcut"/>

</aop:aspect>

</aop:config>

分析:

切点定义 :精确匹配transfer()方法执行

通知顺序

  • before:方法执行前开启事务

  • after-returning:方法正常返回时提交

  • after-throwing:方法抛出异常时回滚

  • after:最终关闭连接(类似finally)

  • 异常处理 :通过throwing="ex"捕获异常对象

事务完整性:四种通知组合确保事务的ACID特性

账户实体类(Account.java)

public class Account {

private String name;

private Double money;

// 构造器/getter/setter

public Account() {}

public Account(String name, Double money) {

this.name = name;

this.money = money;

}

// 省略getter/setter

}

连接工具类(ConnectionUtils.java)

使用ThreadLocal管理连接

public class ConnectionUtils {

// 使用ThreadLocal保证线程安全

private static final ThreadLocal<Connection> tl = new ThreadLocal<>();

// 数据源(通过Spring注入)

private DataSource dataSource;

public void setDataSource(DataSource dataSource) {

this.dataSource = dataSource;

}

// 获取当前线程的连接

public Connection getConnection() throws SQLException {

Connection conn = tl.get();

if (conn == null) {

conn = dataSource.getConnection();

tl.set(conn);

}

return conn;

}

// 解除当前线程的连接绑定

public void removeConnection() {

tl.remove();

}

}

DAO接口以及DAO实现

public interface AccountDao {

Account findAccountByName(String name) throws SQLException;

void updateAccount(Account account) throws SQLException;

}

public class AccountDaoImpl implements AccountDao {

private QueryRunner runner;

private ConnectionUtils connectionUtils;

public void setRunner(QueryRunner runner) {

this.runner = runner;

}

public void setConnectionUtils(ConnectionUtils connectionUtils) {

this.connectionUtils = connectionUtils;

}

@Override

public Account findAccountByName(String name) throws SQLException {

return runner.query(

connectionUtils.getConnection(),

"SELECT * FROM account WHERE name = ?",

new BeanHandler<>(Account.class),

name

);

}

@Override

public void updateAccount(Account account) throws SQLException {

runner.update(

connectionUtils.getConnection(),

"UPDATE account SET money = ? WHERE name = ?",

account.getMoney(),

account.getName()

);

}

}

编写测试类

public class TransferTest {

public static void main(String[] args) {

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

AccountService service = context.getBean("accountService", AccountService.class);

try {

// 执行转账:Tom给Jerry转500元

service.transfer("Tom", "Jerry", 500.0);

System.out.println("转账成功!");

} catch (Exception e) {

System.out.println("转账失败:" + e.getMessage());

e.printStackTrace();

}

}

}