写在前面“就算 finally 块里有 returntry 里的 return 也会先执行只不过 finally 的 return 覆盖了它”——这是我以前的理解直到我亲手写了一段代码才惊觉自己错得离谱。不仅如此很多 Spring 事务“莫名其妙”失效的问题追根溯源往往也和 try-catch-finally 中对 return 和异常的处理方式脱不了干系。今天我们就用几个“反直觉”的例子彻底搞懂 try-catch-finally 中 return 的真实行为并揭开它与 SpringTransactional失效之间的隐秘联系。一、三个小实验让你怀疑人生的 return 顺序实验1try 中有 returnfinally 中没有 returnpublic static int test1() { int i 1; try { return i; // ① } finally { i 2; // ② } }你猜结果是多少1 还是 2运行结果1。解释当 try 块执行到return i时JVM 会先计算返回值此时 i1并将这个值暂存暂存于操作数栈或局部变量表。然后执行 finally 块i2。finally 执行完毕后方法返回之前暂存的那个值1。所以 finally 中对 i 的修改并不会改变返回值。实验2finally 中也有 returnpublic static int test2() { int i 1; try { return i; // ① } finally { i 2; return i; // ② } }结果2。解释当 finally 块中有return语句时finally 中的 return 会“覆盖”掉 try 中的 return。这是因为 finally 块在 try 的 return 之前执行但 finally 自己的 return 会立即结束方法。实验3try 和 finally 中都 return但 try 抛出异常public static int test3() { int i 1; try { i i / 0; // 抛出异常 return i; } catch (Exception e) { return 3; // ③ } finally { return 4; // ④ } }结果4。解释无论是否发生异常finally中的 return 永远会“截胡”。即便 catch 块有 returnfinally 的 return 也会覆盖它。所以铁律来了finally 块中不应该包含 return 语句——它会屏蔽 try 和 catch 中的返回值并吞掉异常。二、JVM 视角finally 的“强制主义”从 JVM 字节码层面看finally块的代码会被复制到每个return和异常抛出路径之前。也就是说无论方法从 try、catch 还是异常出口结束finally都会在方法真正返回之前执行。如果finally没有return那么方法的返回值由 try 或 catch 中的return决定值暂存机制。如果finally有return那么finally的return才是最终的返回出口try/catch 中的return相当于被忽略了。三、与 Spring 事务失效的“隐秘联系”Transactional注解的事务回滚机制依赖于方法是否抛出特定的异常默认是RuntimeException或Error。如果你在方法内用try-catch捕获了异常并且没有重新抛出Spring 就感知不到异常从而不会回滚事务。而当finally中出现return时问题会变得更加隐蔽。案例1catch 后不抛异常事务不回滚Transactional public void updateUser() { try { userDao.update(); // 可能抛异常 } catch (Exception e) { log.error(发生异常, e); // 并没有重新抛出 } } 即使userDao.update()抛出了异常Spring 也看不到事务仍然会提交。数据错乱的风险极大。案例2finally 中的 return 吞掉了异常Transactional public int updateOrder() { try { orderDao.deductStock(); // 假设抛异常 return 1; } finally { return 0; // 异常被彻底掩盖事务也不会回滚 } }异常不会传播到调用者Spring 的TransactionInterceptor根本接收不到异常。方法正常返回 0事务正常提交。但业务逻辑实际上已经失败了。案例3catch 后重新抛出但 finally 中 return 覆盖Transactional public int updateProduct() { try { productDao.update(); return 1; } catch (Exception e) { throw new RuntimeException(e); // 本应触发回滚 } finally { return 0; // 但这里 return 了异常被吞掉事务不回滚 } }结论在Transactional方法中绝对不要在 finally 块里写 return。否则事务的异常感知机制会被破坏。正确的处理方式Transactional public void doSomething() { try { // 业务逻辑 } catch (Exception e) { // 记录日志 throw e; // 重新抛出让事务感知 // 或者 // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } finally { // 不要 return // 只做资源清理工作 } }四、一张图总结return 与 finally 的博弈五、最佳实践避开这些暗礁永远不要在 finally 块中使用 return。这不仅会吞掉 try/catch 的返回值还可能隐藏异常。在 Transactional 方法中如果捕获了业务异常要么重新抛出要么手动标记回滚。Finally 只做资源清理关闭流、释放锁等不要夹杂业务逻辑。如果你发现自己想在 finally 中 return八成说明设计上需要重构——比如应该用 try-with-resources 或提取方法。以下代码中flag 的最终值是多少finally 中改变 flag 会影响 finally 块内 return 的返回值吗为什么public static int test() { int flag 10; try { flag 20; return flag; } finally { flag 30; return flag; } }欢迎在评论区留下你的答案和分析。