AOP 实现
这里实现的 AOP 只是一个简化版本,主要用于理解"通过代理统一控制事务"的基本思路。它还不具备 Spring AOP 那种完整的扩展能力,例如切点表达式、多个通知、注解解析、异常分类处理等。
本篇重点是:通过一个简单的 ProxyFactory,在业务方法执行前开启事务,在执行成功后提交事务,在出现异常时回滚事务。

AOP(面向切面编程)
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程思想,主要用于处理那些"分散在多个业务模块中,但又不属于核心业务逻辑"的公共功能。
常见的横切逻辑包括:
- 日志记录
- 权限校验
- 性能统计
- 事务管理
- 异常处理
如果不使用 AOP,这些公共逻辑往往会散落在各个 Service 方法中,导致业务代码和非业务代码混在一起。AOP 的作用就是把这些公共逻辑抽离出来,通过代理、字节码增强等方式统一织入到目标方法前后。
在本文的例子中,我们要抽离的公共逻辑就是事务控制。
AOP的核心概念
AOP 和 OOP 的关注点不同。OOP 更强调对象、封装、继承和多态;AOP 更强调把一类横向重复出现的逻辑抽出来,统一应用到多个目标方法上。
下面简单说明几个核心概念。
切面(Aspect)
切面可以理解为一组横切逻辑的封装。
例如事务管理就是一个切面:它不属于转账业务本身,但转账方法执行前需要开启事务,执行成功后需要提交事务,执行失败后需要回滚事务。
在本文中,WzkTransactionManager 和 ProxyFactory 组合起来,承担了简易切面的作用。
连接点(Joinpoint)
连接点指程序执行过程中可以插入增强逻辑的位置。
在 Java Web 项目中,最常见的连接点就是方法执行。例如:
java
wzkTransferService.transfer("1", "2", 100);
这个 transfer 方法就是一个可以被代理增强的位置。
通知(Advice)
通知指的是切面在连接点上执行的具体动作。
常见通知类型包括:
- 前置通知:目标方法执行前执行。
- 后置通知:目标方法执行后执行。
- 环绕通知:目标方法执行前后都可以控制。
- 异常通知:目标方法抛出异常后执行。
- 返回通知:目标方法正常返回后执行。
本文中的事务控制更接近"环绕通知":在目标方法执行前开启事务,在目标方法执行后提交事务,如果出现异常则回滚事务。
切点(Pointcut)
切点用于描述哪些连接点需要被增强。
Spring AOP 中可以通过表达式、注解等方式匹配目标方法。本文暂时不实现复杂切点,只是在 Servlet 初始化时手动指定要代理的 Service 对象。
织入(Weaving)
织入指的是把切面逻辑应用到目标对象上的过程。
常见织入方式包括:
- 编译时织入:编译阶段完成,例如 AspectJ。
- 类加载时织入:类加载过程中修改字节码。
- 运行时织入:程序运行时通过代理完成,例如 Spring AOP。
本文采用的是运行时织入,也就是通过 JDK 动态代理或 CGLIB 代理,在运行时为目标对象增加事务控制能力。
Resources
下面是本次使用的 XML 配置。这里的 Bean 会由自定义 BeanFactory 读取并创建。
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!-- BeanFactory 类会处理这块配置 -->
<beans>
<!-- WzkConnectionUtils 交给容器管理 -->
<bean id="wzkConnectionUtils" class="wzk.utils.WzkConnectionUtils"></bean>
<!-- JdbcWzkAccountDaoImpl 依赖 WzkConnectionUtils -->
<bean id="wzkAccountDao" class="wzk.dao.impl.JdbcWzkAccountDaoImpl">
<!-- name 是成员变量名,ref 是容器中被引用对象的 id -->
<property name="WzkConnectionUtils" ref="wzkConnectionUtils"/>
</bean>
<!-- WzkTransferServiceImpl 依赖 WzkAccountDao -->
<bean id="wzkTransferService" class="wzk.service.impl.WzkTransferServiceImpl">
<property name="WzkAccountDao" ref="wzkAccountDao"></property>
</bean>
<!-- WzkTransactionManager 依赖 WzkConnectionUtils -->
<bean id="wzkTransactionManager" class="wzk.utils.WzkTransactionManager">
<property name="WzkConnectionUtils" ref="wzkConnectionUtils"></property>
</bean>
<!-- ProxyFactory 依赖 WzkTransactionManager -->
<bean id="proxyFactory" class="wzk.factory.ProxyFactory">
<property name="WzkTransactionManager" ref="wzkTransactionManager"></property>
</bean>
</beans>
对应的截图如下所示:

WzkTransactionManager
事务管理器主要负责开启事务、提交事务和回滚事务。这里是对 JDBC 事务操作的一层简单封装。
java
/**
* 事务控制器
* 这里是对 JDBC 事务操作的简单封装
*
* @author wzk
* @date 11:31 2024/11/19
**/
public class WzkTransactionManager {
private WzkConnectionUtils wzkConnectionUtils;
public void setWzkConnectionUtils(WzkConnectionUtils wzkConnectionUtils) {
this.wzkConnectionUtils = wzkConnectionUtils;
}
public void beginTransaction() throws SQLException {
wzkConnectionUtils.getCurrentConnection().setAutoCommit(false);
}
public void commit() throws SQLException {
wzkConnectionUtils.getCurrentConnection().commit();
}
public void rollback() throws SQLException {
wzkConnectionUtils.getCurrentConnection().rollback();
}
}
对应的截图如下所示:

Proxy
ProxyFactory
ProxyFactory 是代理工厂,主要负责给目标对象生成代理对象。
这里提供了两种方式:
- JDK 动态代理:要求目标对象实现接口。
- CGLIB 动态代理:可以代理普通类,但不能代理
final类和final方法。
本文在 Servlet 中主要使用的是 JDK 动态代理。
java
/**
* 代理工厂
* 通过动态代理统一增加事务控制逻辑
*
* @author wzk
* @date 11:33 2024/11/19
**/
public class ProxyFactory {
private WzkTransactionManager wzkTransactionManager;
public void setWzkTransactionManager(WzkTransactionManager wzkTransactionManager) {
this.wzkTransactionManager = wzkTransactionManager;
}
/**
* JDK 动态代理
* 适用于目标对象实现了接口的情况
*
* @author wzk
* @date 11:35 2024/11/19
**/
public Object getJdkProxy(Object object) {
return Proxy.newProxyInstance(
object.getClass().getClassLoader(),
object.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result;
try {
// 开启事务
wzkTransactionManager.beginTransaction();
// 执行目标方法
result = method.invoke(object, args);
// 提交事务
wzkTransactionManager.commit();
return result;
} catch (Exception e) {
// 出现异常时回滚事务
wzkTransactionManager.rollback();
throw e;
}
}
}
);
}
/**
* CGLIB 动态代理
* 适用于目标对象没有实现接口的情况
*
* @author wzk
* @date 13:57 2024/11/19
**/
public Object getCglibProxy(Object object) {
return Enhancer.create(
object.getClass(),
new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object result;
try {
wzkTransactionManager.beginTransaction();
result = method.invoke(object, args);
wzkTransactionManager.commit();
return result;
} catch (Exception e) {
wzkTransactionManager.rollback();
throw e;
}
}
}
);
}
}
对应的截图如下如下:

Controller
WzkServlet
在 WzkServlet 中,我们不再直接使用原始的 WzkTransferService,而是从 ProxyFactory 中获取一个代理对象。
这样调用 transfer 方法时,实际执行流程就变成了:
- 代理对象先开启事务。
- 调用原始 Service 的
transfer方法。 - 如果执行成功,提交事务。
- 如果执行失败,回滚事务。
java
@WebServlet(name="wzkServlet", urlPatterns = "/wzkServlet")
public class WzkServlet extends HttpServlet {
// ========================== 2 ==========================
// 由 BeanFactory 处理
// private WzkTransferService wzkTransferService = (WzkTransferService) BeanFactory.getBean("wzkTransferService");
// =======================================================
// ========================== 3 ==========================
// 另一种方式 相同的
// private WzkTransferService wzkTransferService;
// @Override
// public void init() throws ServletException {
// super.init();
// this.wzkTransferService = (WzkTransferService) BeanFactory.getBean("wzkTransferService");
// }
// ======================================================
// ================== 4 ======================
// 获取代理对象,也就是带有事务控制能力的 Service
private WzkTransferService wzkTransferService;
@Override
public void init() throws ServletException {
super.init();
// 从 BeanFactory 中获取代理工厂
ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
// 对原始 Service 生成 JDK 动态代理
this.wzkTransferService = (WzkTransferService) proxyFactory.getJdkProxy(
BeanFactory.getBean("wzkTransferService"));
}
// ===========================================
@Override
protected void doGet(javax.servlet.http.HttpServletRequest req, javax.servlet.http.HttpServletResponse resp) throws javax.servlet.ServletException, IOException {
System.out.println("=== WzkServlet doGet ===");
// ======================= 1 =============================
// 如果没有 BeanFactory 和代理工厂,就需要手动创建对象并维护依赖关系
// 组装 DAO,DAO 层依赖 ConnectionUtils 和 DruidUtils
// JdbcWzkAccountDaoImpl jdbcWzkAccountDaoImpl = new JdbcWzkAccountDaoImpl();
// jdbcWzkAccountDaoImpl.setConnectionUtils(new WzkConnectionUtils());
// 组装 Service
// WzkTransferServiceImpl wzkTransferService = new WzkTransferServiceImpl();
// wzkTransferService.setWzkAccountDao(jdbcWzkAccountDaoImpl);
// ======================================================
// 执行业务逻辑
System.out.println("wzkTransferService: " + wzkTransferService);
try {
wzkTransferService.transfer("1", "2", 100);
} catch (Exception e) {
e.printStackTrace();
System.out.println("=== transfer error ====");
}
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().print("=== WzkServlet doGet ===");
}
}
对应的截图如下所示:

测试运行1
先测试正常执行的情况。
访问接口后,可以看到程序顺利执行:

对应的数据库数据也正常发生了变化:

这说明在没有异常的情况下,代理对象成功完成了事务提交。
测试运行2
接着测试出现异常时的回滚情况。
可以在 WzkTransferServiceImpl 的 transfer 方法中,手动加入一个异常,例如 1 / 0。
java
@Override
public void transfer(String fromCard, String toCard, int money) throws Exception {
WzkAccount from = wzkAccountDao.selectWzkAccount(fromCard);
WzkAccount to = wzkAccountDao.selectWzkAccount(toCard);
from.setMoney(from.getMoney() - money);
to.setMoney(to.getMoney() + money);
int fromResult = wzkAccountDao.updateWzkAccount(from);
// 故意制造异常,测试事务回滚
int i = 1 / 0;
int toResult = wzkAccountDao.updateWzkAccount(to);
System.out.println("transfer fromResult: " + fromResult + " toResult: " + toResult);
}
对应的代码截图如下所示。执行之后,数据库中的数据应该不会变化,因为事务管理器会执行回滚:

接口调用结果如下,可以看到程序确实报错了:

数据库数据如下:

可以看到,即使前面已经执行了转出账户的更新,只要后续出现异常,代理对象就会调用事务管理器进行回滚,最终数据库数据不会被部分修改。
这就是一个简易 AOP 事务控制的基本过程:业务方法只关心转账逻辑,事务开启、提交、回滚都交给代理对象统一处理。
TL;DR
- 场景 :用 JDK 动态代理 + 简易
ProxyFactory把事务控制从WzkTransferService中抽离出来,验证 AOP"业务与非业务逻辑分离"的核心思路。 - 结论 :
ProxyFactory在方法执行前开启事务、正常返回后提交事务、捕获到Exception时回滚并向上抛出;只要把代理对象注入WzkServlet,业务代码无须任何事务样板。 - 产出 :
beans.xml+WzkTransactionManager+ProxyFactory+WzkServlet完整可运行链路,以及"正常提交 vs 异常回滚"两套对照截图,可作为后续学习 Spring@Transactional的前置铺垫。
SEO 摘要(约 250 字)
AOP(Aspect-Oriented Programming,面向切面编程)是 Java 后端处理横切关注点(事务、日志、权限、异常)的核心思想。Spring 6.x(2024--2026 年间持续迭代)依然沿用 JDK 动态代理 + CGLIB 双轨制:JDK 代理基于 java.lang.reflect.Proxy 与 InvocationHandler,要求目标对象实现接口;CGLIB 通过字节码生成子类实现代理,可代理普通类但不能代理 final 类/final 方法。本文给出一套"教学级 AOP"实现:通过自定义 BeanFactory 读取 beans.xml,由 WzkTransactionManager 封装 JDBC 的 setAutoCommit(false) / commit() / rollback(),再由 ProxyFactory 在方法执行前开启事务、正常返回后提交、捕获 Exception 时回滚。WzkServlet.init() 从容器拿到 ProxyFactory,对 WzkTransferService 生成 JDK 代理,调用 transfer("1","2",100) 即可触发完整事务流程。文章附带正常执行与 1/0 异常两轮对比截图,直观展示部分更新被事务回滚撤销的过程,是理解 Spring @Transactional 与 ProxyFactoryBean 的入门铺垫。
版本矩阵
| 模块 / 关键点 | 状态 | 说明 |
|---|---|---|
| AOP 核心概念(Aspect / Joinpoint / Advice / Pointcut / Weaving) | ✅ 已验证 | 与 Spring AOP 官方术语一致,可在 Spring 文档中对照阅读 |
Proxy.newProxyInstance + InvocationHandler 实现 JDK 动态代理 |
✅ 已验证 | Java SE 自带反射 API,JDK 8+ 稳定可用 |
| CGLIB 通过子类继承实现方法拦截 | ✅ 已验证 | Spring 6.x 已将 CGLIB 迁入 spring-core 内部维护,规避外部 cglib 与 JDK 17+ 模块化冲突 |
| Spring AOP 默认在目标实现接口时使用 JDK 代理,否则使用 CGLIB | ✅ 已验证 | Spring 6.x 文档与社区资料一致;可通过 @EnableTransactionManagement(proxyTargetClass = true) 强制 CGLIB |
WzkTransactionManager 封装 setAutoCommit / commit / rollback |
✅ 已验证 | JDBC 标准 API,连接绑定到 ThreadLocal 即可保证同一线程复用 |
ProxyFactory.getJdkProxy 在 catch 中回滚并 rethrow |
✅ 已验证 | 是本文事务链路能正确工作的关键 |
| 织入方式(编译时 / 类加载时 / 运行时)三类 | ✅ 已验证 | 本文示例为运行时织入,对应 Spring AOP 模式;AspectJ 走编译时或类加载时 |
自定义 BeanFactory 读取 beans.xml |
✅ 已验证 | 教学用 XML 容器,与 Spring XmlBeanFactory 思路一致 |
| 完整 Spring 切点表达式 / 注解驱动 / 通知顺序 | ❌ 不在本文范围 | 教学版只展示运行时环绕通知;Spring 提供 @Aspect + execution(...) 全套能力 |
分布式事务 / @Transactional 传播行为 / 只读优化 |
❌ 不在本文范围 | 单库单连接事务示意,生产应使用 Spring 事务管理器配置传播与隔离级别 |
错误速查卡
| 症状 | 根因 | 定位 | 修复 |
|---|---|---|---|
ProxyFactory.getJdkProxy(...) 报 IllegalArgumentException: ... is not an interface |
目标类没有实现任何接口,object.getClass().getInterfaces() 长度为 0 |
在调用前打印 target.getClass().getInterfaces().length |
改用 getCglibProxy,或给目标类补充接口 |
事务开启/提交报 SQLException: Connection is already closed |
WzkConnectionUtils 没有用 ThreadLocal 把连接绑到当前线程,每次都拿到新连接 |
在 WzkConnectionUtils.getCurrentConnection 里检查是否从 ThreadLocal 取 |
改用 ThreadLocal<Connection> 绑定,Service 与 TransactionManager 共用同一个连接 |
| 出现异常时事务没有回滚,数据库部分更新 | invoke 里 catch (Throwable) 之前 result = method.invoke(object, args) 抛了 Error 而不是 Exception |
打印异常类型确认是 Exception / Error |
改为 catch (Throwable t),或确保业务异常继承 Exception |
CGLIB 报 Cannot subclass final class |
目标类或方法被 final 修饰,CGLIB 无法继承 |
看目标类/方法声明 | 去掉 final,或改用 JDK 代理(前提是目标实现接口) |
CGLIB 在 JDK 17+ 报 IllegalAccessError / 模块访问错误 |
老版本 cglib-nodep 与 JDK 模块化不兼容 |
看堆栈是否涉及 sun.reflect / java.lang 模块访问 |
升级 Spring 6.x(自带 spring-cglib),或在启动参数加 --add-opens java.lang/java.lang.reflect=ALL-UNNAMED |
| 业务方法正常返回但数据库没变化 | setAutoCommit(false) 没生效,或连接不是 getCurrentConnection() 拿到的同一个 |
在 beginTransaction / commit 里打印 connection.hashCode() 比对 |
保证 DAO 与 TransactionManager 共享同一个 ThreadLocal 连接 |
Servlet 启动报 ClassCastException: com.sun.proxy.$Proxy42 cannot be cast to WzkTransferService |
JDK 动态代理对象是 $Proxy,强转目标实现类失败 |
看错误堆栈确认强转目标 | 只强转为目标实现的接口,例如 WzkTransferService,不要强转到实现类 |
注解 @WebServlet 不生效,接口 404 |
Servlet 容器没扫描到注解类 | 确认部署描述 web.xml 没有覆盖注解,或容器版本过低 |
升级到 Servlet 3.0+ 容器;或在 web.xml 显式声明 Servlet |
ProxyFactory 注入 WzkTransactionManager 为 null |
BeanFactory 没处理 <property> 注入,或 setter 名大小写不匹配 |
在 setWzkTransactionManager 里打印参数 |
保证 <property name="WzkTransactionManager"> 的 name 与 setter 名一致 |
调用 transfer 后事务未提交、连接一直占用 |
业务方法内部捕获了异常并吞掉,导致代理 catch 不到 | 检查业务方法是否有 try { ... } catch (Exception e) { log(...); } |
让异常抛出到代理层,或在业务 catch 后重新 throw |
测试正常时 commit 报 Cannot commit when autoCommit is enabled |
拿到的连接是新的、没经过 beginTransaction 处理 |
看连接是否被复用 | 在 getCurrentConnection 里检查并 setAutoCommit(false) 后再返回 |
| 部分接口切了事务、部分没切 | 只有从 ProxyFactory.getJdkProxy(...) 拿到的对象才带事务,直接 new WzkTransferServiceImpl() 调用没事务 |
grep 项目里直接 new Service 的位置 |
强制所有调用走 BeanFactory 拿代理对象,或迁移到 Spring @Transactional |
参考来源
- Spring AOP 官方文档:docs.spring.io/spring-fram...
- Spring 6.x 动态代理与 CGLIB 实现分析:cloud.tencent.com/developer/a...
java.lang.reflect.ProxyJavadoc:docs.oracle.com/javase/8/do...@EnableTransactionManagement注解与proxyTargetClass配置:www.jb51.net/program/298...- Spring
ProxyFactoryBean设计解析:blog.csdn.net/mashaokang1...
作者:武子康的个人博客