第一部分:业务场景与问题引入
1.1 业务场景
要求对两个账户进行保存(模拟转账),这两个账户的保存操作必须同时成功或同时失败,构成一个最小逻辑执行单元(事务)。
业务层 AccountServiceImpl 代码如下:
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
/**
* 对 saveAll 方法进行增强
*/
@Override
public void saveAll(Account account1, Account account2) throws SQLException {
// 保存 1 账号
accountDao.save(account1);
// 模拟异常
// int a = 1/0;
// 保存 2 账号
accountDao.save(account2);
}
}
1.2 问题所在
- 若只有第一个账号保存成功,第二个账号保存失败(如发生异常),数据就不一致。
- 转账场景:一个账户扣钱,另一个没加上,钱就"转丢了"。
难点:理解为什么这两个保存操作必须绑在一起。因为它们是同一个事务,中途不能断开。
第二部分:传统解决方案的痛点
2.1 手动控制事务
做法:用 JDBC 工具类在业务层手动控制事务。
核心逻辑:startTransaction(底层 setAutoCommit(false))关闭自动提交,执行业务操作,无异常则 commit,有异常则 rollback,最后 close 释放资源。
2.2 持久层配合
持久层的 save 方法在插入数据后,不能关闭 Connection,因为事务要在业务层控制。
public class AccountDaoImpl implements AccountDao {
@Override
public void save(Account account) throws SQLException {
// 获取连接(注意 conn 不能关闭)
Connection conn = TxUtils.getConnection();
String sql = "insert into account values (null,?,?)";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, account.getName());
stmt.setDouble(2, account.getMoney());
stmt.executeUpdate();
// 关闭资源,conn 不能关闭
stmt.close();
}
}
2.3 痛点分析
- 代码重复 :如果业务里有 10 套类似操作,就要在每个业务方法里写一遍
try-catch和事务控制代码。 - 侵入性强:业务代码里混杂了大量事务控制的非业务代码,不符合解耦原则。
- 维护困难:改动事务逻辑需要去每个业务方法里修改。
重点:需要把事务控制逻辑向上抽取,动态地在业务方法执行前、执行后、抛出异常时织入处理,这就是 AOP(面向切面编程)的核心思想。
第三部分:JDK 动态代理的引入
既然不能在业务代码里写死,能不能找一个"中间人",帮我们完成开启事务、提交、回滚的事情?这就是动态代理。
3.1 生成代理对象的代码
编写 JdkProxy 类,实现 InvocationHandler 接口,用于对目标对象的方法进行增强。
package com.qcbyjy.demo1;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 传入目标对象,生成该对象的代理对象,返回。对目标对象的方法进行增强
*/
public class JdkProxy {
/**
* 获取代理对象,返回,增强目标对象的方法
*/
public static Object getProxy(AccountService accountService) {
/**
* 使用 Jdk 的动态代理生成代理对象
*/
Object proxy = Proxy.newProxyInstance(
JdkProxy.class.getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 调用代理对象的方法,invoke 方法就会去执行
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
// 开启事务
TxUtils.startTransaction();
// 对目标对象的方法进行增强
result = method.invoke(accountService, args);
// 提交事务
TxUtils.commit();
} catch (Exception e) {
e.printStackTrace();
TxUtils.rollback();
} finally {
// 关闭资源
TxUtils.close();
}
return result;
}
});
return proxy;
}
}
3.2 JDK 动态代理核心 API
Proxy.newProxyInstance 需要传入三个参数:
| 参数 | 说明 |
|---|---|
ClassLoader loader |
代理类的类加载器(如 JdkProxy.class.getClassLoader()) |
Class<?>[] interfaces |
代理的目标对象的接口(如 accountService.getClass().getInterfaces()) |
InvocationHandler h |
调用处理程序,内部实现增强逻辑 |
重点:JDK 动态代理基于接口实现,所以目标对象必须实现至少一个接口。
易错点:传入的 accountService 实际应为接口类型。所有实现了该接口的类都满足代理条件。
3.3 测试与执行流程
@Test
public void run1() throws SQLException {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_demo1.xml");
// 获取 service 对象(目标对象)
AccountService accountService = (AccountService) ac.getBean("accountService");
// 准备两个账户
Account account1 = new Account();
account1.setName("熊大");
account1.setMoney(1000d);
Account account2 = new Account();
account2.setName("美羊羊");
account2.setMoney(1000d);
// 生成代理对象
Object proxyobj = JdkProxy.getProxy(accountService);
// 强转
AccountService proxy = (AccountService) proxyobj;
// 调用代理对象方法
proxy.saveAll(account1, account2);
}
执行流程:
- 测试类调用代理对象的
saveAll。 - 代理对象拦截调用,进入
invoke方法。 invoke内部先开事务,再通过反射method.invoke(accountService, args)调用原对象的目标方法。- 如果发生除零异常,目标方法抛出,被
catch捕获,执行rollback回滚。 - 如果没有异常,执行
commit提交。 finally中统一关闭资源。
重点:由于异常抛出,代理类没有提交事务,导致数据添加不进去。这验证了代理对象能够拦截异常并回滚,从而实现事务控制。
3.4 静态代理与动态代理的区别
- 静态代理:基于接口实现,需要手动编写代理类。
- JDK 动态代理:基于接口实现,通过反射动态生成代理类。
- CGLIB 代理:基于继承实现,通过生成目标类的子类来代理。逻辑相似但机制不同。
易错点:JDK 动态代理和静态代理都是基于接口,不要误以为静态也用继承。CGLIB 才是基于继承。
第四部分:从动态代理到 AOP 概念
4.1 为什么提出 AOP?
手动写 JDK 动态代理类仍然繁琐:每个接口都要写一套代理逻辑。AOP 底层就是封装了 JDK 动态代理(或 CGLIB 代理)。我们只需写个切面类、加注解并做简单配置,就能省去复杂的代理代码。
4.2 AOP 专业术语与动态代理的映射
| AOP 术语 | 动态代理中的对应逻辑 |
|---|---|
| 前置通知 | 动态代理中开启事务 |
| 后置通知 | 动态代理中提交事务 |
| 异常通知 | 动态代理中回滚事务 |
| 最终通知 | 动态代理中关闭资源 |
重点:AOP 本质是用约定的术语替代了手动编写代理类的代码。开发中我们只需在配置文件中通过 AOP 配置即可生效,切面类和通知方法自己写。
易错点:别把后置通知和最终通知混淆。后者不管成败都关资源,前者仅正常提交。
4.3 AOP 概念初步引入
- AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的重要内容。
- 它采取横向抽取机制,取代了传统纵向继承体系重复性代码(如性能监视、事务管理、安全检查、缓存)。
- 核心目的:在不修改源代码的前提下,对程序进行增强。
重点:AOP 不止做事务控制,还能做日志监控、程序增强扩展等,后续会深入学习其实现方式和概念。