☰
JDK 动态代理动态织入事务详解
2026/10/2 6:54:49 网站建设 项目流程

JDK 动态代理动态织入事务详解

一、目标:手动实现事务代理

Spring 的@Transactional底层就是通过动态代理织入事务的。我们手动实现一个简化版,彻底理解这个过程。

需求:Service 方法执行前开启事务,执行成功提交事务,抛出异常回滚事务。

调用 Service 方法 → 代理拦截 → 开启事务 → 执行目标方法 → 成功 → 提交 → 异常 → 回滚 → 抛出异常

二、完整代码实现

1. 事务管理器

模拟一个事务管理器,负责开启、提交、回滚事务。

publicclassTransactionManager{// 模拟 ThreadLocal 保存当前线程的事务状态privatestaticfinalThreadLocal<Boolean>transactionStatus=newThreadLocal<>();publicvoidbegin(){System.out.println("【事务】开启事务");transactionStatus.set(true);}publicvoidcommit(){System.out.println("【事务】提交事务");transactionStatus.remove();}publicvoidrollback(){System.out.println("【事务】回滚事务");transactionStatus.remove();}publicbooleanisActive(){returnBoolean.TRUE.equals(transactionStatus.get());}}

2. 抽象主题

publicinterfaceUserService{voidsave(Useruser);UserfindById(Longid);voidbatchSave(List<User>users);}

3. 真实主题

publicclassUserServiceImplimplementsUserService{@Overridepublicvoidsave(Useruser){System.out.println("保存用户:"+user.getName());// 模拟数据库操作}@OverridepublicUserfindById(Longid){System.out.println("查询用户:"+id);returnnewUser(id,"张三");}@OverridepublicvoidbatchSave(List<User>users){for(Useruser:users){System.out.println("批量保存:"+user.getName());}// 模拟异常if(users.size()>5){thrownewRuntimeException("批量保存失败");}}}

4. 事务 InvocationHandler

核心逻辑在invoke()方法中:方法执行前开启事务,成功提交,异常回滚。

publicclassTransactionInvocationHandlerimplementsInvocationHandler{// 目标对象privatefinalObjecttarget;// 事务管理器privatefinalTransactionManagertransactionManager;publicTransactionInvocationHandler(Objecttarget,TransactionManagertransactionManager){this.target=target;this.transactionManager=transactionManager;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{// 1. 判断方法是否需要事务if(!needTransaction(method)){returnmethod.invoke(target,args);}// 2. 开启事务transactionManager.begin();Objectresult=null;try{// 3. 执行目标方法result=method.invoke(target,args);// 4. 方法正常返回,提交事务transactionManager.commit();returnresult;}catch(Exceptione){// 5. 方法抛出异常,回滚事务transactionManager.rollback();// 6. 把原始异常抛出去throwe.getCause()!=null?e.getCause():e;}}/** * 判断方法是否需要事务 * 查询方法不需要事务,增删改方法需要 */privatebooleanneedTransaction(Methodmethod){StringmethodName=method.getName();returnmethodName.startsWith("save")||methodName.startsWith("update")||methodName.startsWith("delete")||methodName.startsWith("batch");}}

5. 客户端调用

publicclassClient{publicstaticvoidmain(String[]args){// 1. 创建真实对象UserServicetarget=newUserServiceImpl();// 2. 创建事务管理器TransactionManagertxManager=newTransactionManager();// 3. 创建代理对象UserServiceproxy=(UserService)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),newTransactionInvocationHandler(target,txManager));// 4. 通过代理调用System.out.println("===== 正常保存 =====");proxy.save(newUser(1L,"张三"));System.out.println("\n===== 正常查询(无事务)=====");proxy.findById(1L);System.out.println("\n===== 批量保存异常 =====");try{proxy.batchSave(Arrays.asList(newUser(1L,"A"),newUser(2L,"B"),newUser(3L,"C"),newUser(4L,"D"),newUser(5L,"E"),newUser(6L,"F")));}catch(Exceptione){System.out.println("捕获异常:"+e.getMessage());}}}

6. 输出结果

===== 正常保存 ===== 【事务】开启事务 保存用户:张三 【事务】提交事务 ===== 正常查询(无事务)===== 查询用户:1 ===== 批量保存异常 ===== 【事务】开启事务 批量保存:A 批量保存:B 批量保存:C 批量保存:D 批量保存:E 批量保存:F 【事务】回滚事务 捕获异常:批量保存失败

三、代理类的结构

Proxy.newProxyInstance()生成的代理类大致如下:

publicfinalclass$Proxy0extendsProxyimplementsUserService{privatestaticMethodm0;// saveprivatestaticMethodm1;// findByIdprivatestaticMethodm2;// batchSavepublic$Proxy0(InvocationHandlerh){super(h);}@Overridepublicfinalvoidsave(Useruser){try{super.h.invoke(this,m0,newObject[]{user});}catch(Throwablee){thrownewUndeclaredThrowableException(e);}}@OverridepublicfinalUserfindById(Longid){try{return(User)super.h.invoke(this,m1,newObject[]{id});}catch(Throwablee){thrownewUndeclaredThrowableException(e);}}@OverridepublicfinalvoidbatchSave(List<User>users){try{super.h.invoke(this,m2,newObject[]{users});}catch(Throwablee){thrownewUndeclaredThrowableException(e);}}}

每个方法都把调用转发到InvocationHandler.invoke(),事务逻辑就在这里织入。

四、关键细节

1. 异常处理

method.invoke()抛出的异常会被包装成InvocationTargetException,需要取出原始异常:

catch(Exceptione){transactionManager.rollback();// e 是 InvocationTargetException,需要 getCause()throwe.getCause()!=null?e.getCause():e;}

2. 事务传播

上面的实现是简化版,每次调用都开启新事务。真实场景中需要处理事务传播:

  • 当前没有事务时,开启新事务。
  • 当前已有事务时,加入当前事务(REQUIRED)。
  • 当前已有事务时,挂起当前事务,开启新事务(REQUIRES_NEW)。

简化版传播处理:

@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{if(!needTransaction(method)){returnmethod.invoke(target,args);}// 如果当前已有事务,直接加入if(transactionManager.isActive()){returnmethod.invoke(target,args);}// 没有事务,开启新事务transactionManager.begin();try{Objectresult=method.invoke(target,args);transactionManager.commit();returnresult;}catch(Exceptione){transactionManager.rollback();throwe.getCause()!=null?e.getCause():e;}}

3. 只读事务

查询方法可以标记为只读,优化性能:

privatebooleanisReadOnly(Methodmethod){Stringname=method.getName();returnname.startsWith("find")||name.startsWith("get")||name.startsWith("query")||name.startsWith("select");}

4. 事务超时

记录事务开始时间,超时后自动回滚:

longstart=System.currentTimeMillis();try{Objectresult=method.invoke(target,args);longcost=System.currentTimeMillis()-start;if(cost>timeout){transactionManager.rollback();thrownewTransactionTimeoutException("事务超时");}transactionManager.commit();returnresult;}catch(Exceptione){transactionManager.rollback();throwe.getCause()!=null?e.getCause():e;}

五、内部调用失效问题

这是动态代理最经典的坑。同一个类中方法 A 调用方法 B,B 的事务不会生效。

publicclassUserServiceImplimplementsUserService{@Overridepublicvoidsave(Useruser){// 直接调用 this.batchSave(),不经过代理batchSave(Arrays.asList(user,user));}@OverridepublicvoidbatchSave(List<User>users){// 事务不生效}}

原因:save()内部调用batchSave()时,this是原始对象,不是代理对象。只有外部通过代理调用才会触发InvocationHandler.invoke()。

解决方案一:注入自身代理

@ServicepublicclassUserServiceImplimplementsUserService{@AutowiredprivateUserServiceself;// 注入代理对象@Overridepublicvoidsave(Useruser){self.batchSave(Arrays.asList(user,user));// 通过代理调用}@OverridepublicvoidbatchSave(List<User>users){// 事务生效}}

解决方案二:使用 AopContext

@ServicepublicclassUserServiceImplimplementsUserService{@Overridepublicvoidsave(Useruser){((UserService)AopContext.currentProxy()).batchSave(Arrays.asList(user,user));}}

需要在配置类上添加@EnableAspectJAutoProxy(exposeProxy = true)。

解决方案三:拆分类

把batchSave移到另一个 Bean 中,通过注入调用。

六、JDK 动态代理的局限

局限说明解决方案
必须实现接口目标类没有接口时无法使用用 CGLIB
只能代理接口方法实现类中额外的方法无法代理用 CGLIB
性能开销反射调用比直接调用慢用 CGLIB 或缓存 Method
内部调用失效this 调用不经过代理注入自身代理或 AopContext
构造方法不执行代理类不调用目标类构造方法无影响,目标对象已创建

七、Spring 中事务代理的完整链路

Spring 的@Transactional底层由TransactionInterceptor实现,它本质上就是一个MethodInterceptor(CGLIB)或配合InvocationHandler(JDK)。

调用 @Transactional 方法 → 代理对象拦截 → TransactionInterceptor.invoke() → 获取 @Transactional 属性(传播行为、隔离级别、超时、只读) → 创建/加入事务 → 执行目标方法 → 成功 → commit → 异常 → rollback(根据 rollbackFor 判断)

核心类:

类作用
TransactionInterceptor事务拦截器,织入事务逻辑
PlatformTransactionManager事务管理器,开启/提交/回滚
TransactionAttributeSource解析@Transactional注解属性
TransactionInfo保存当前事务状态

八、总结

维度核心要点
核心机制Proxy.newProxyInstance()+InvocationHandler
织入逻辑在invoke()中前后插入事务开启、提交、回滚
事务管理器负责 begin、commit、rollback
异常处理回滚后抛出原始异常
内部调用不经过代理,事务失效
Spring 对应TransactionInterceptor+PlatformTransactionManager
代理选择有接口用 JDK,无接口用 CGLIB

JDK 动态代理织入事务的本质是:在InvocationHandler.invoke()中,围绕method.invoke(target, args)包裹事务的开启、提交和回滚逻辑。Spring 的@Transactional就是把这段逻辑封装在了TransactionInterceptor中,通过动态代理自动应用到所有需要事务的方法上。理解这个手动实现,就能理解 Spring 事务代理的底层原理,也能解释为什么内部调用会导致事务失效。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询