写在前面
"就算 finally 块里有 return,try 里的 return 也会先执行,只不过 finally 的 return 覆盖了它"------这是我以前的理解,直到我亲手写了一段代码,才惊觉自己错得离谱。不仅如此,很多 Spring 事务"莫名其妙"失效的问题,追根溯源,往往也和 try-catch-finally 中对 return 和异常的处理方式脱不了干系。
今天,我们就用几个"反直觉"的例子,彻底搞懂 try-catch-finally 中 return 的真实行为,并揭开它与 Spring @Transactional 失效之间的隐秘联系。

一、三个小实验:让你怀疑人生的 return 顺序
实验1:try 中有 return,finally 中没有 return
java
public static int test1() {
int i = 1;
try {
return i; // ①
} finally {
i = 2; // ②
}
}
你猜结果是多少? 1 还是 2?
运行结果:1。
解释 :当 try 块执行到 return i 时,JVM 会先计算返回值(此时 i=1),并将这个值暂存(暂存于操作数栈或局部变量表)。然后执行 finally 块(i=2)。finally 执行完毕后,方法返回之前暂存的那个值(1)。所以 finally 中对 i 的修改并不会改变返回值。
实验2:finally 中也有 return
java
public 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 会立即结束方法。
实验3:try 和 finally 中都 return,但 try 抛出异常
java
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 块有 return,finally 的 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 时,问题会变得更加隐蔽。
案例1:catch 后不抛异常,事务不回滚
java
@Transactional
public void updateUser() {
try {
userDao.update(); // 可能抛异常
} catch (Exception e) {
log.error("发生异常", e);
// 并没有重新抛出
}
}
👉 即使 userDao.update() 抛出了异常,Spring 也看不到,事务仍然会提交。数据错乱的风险极大。
案例2:finally 中的 return 吞掉了异常
java
@Transactional
public int updateOrder() {
try {
orderDao.deductStock(); // 假设抛异常
return 1;
} finally {
return 0; // 异常被彻底掩盖,事务也不会回滚
}
}
-
异常不会传播到调用者,Spring 的
TransactionInterceptor根本接收不到异常。 -
方法正常返回 0,事务正常提交。但业务逻辑实际上已经失败了。
案例3:catch 后重新抛出,但 finally 中 return 覆盖
java
@Transactional
public int updateProduct() {
try {
productDao.update();
return 1;
} catch (Exception e) {
throw new RuntimeException(e); // 本应触发回滚
} finally {
return 0; // 但这里 return 了!异常被吞掉,事务不回滚
}
}
结论 :在 @Transactional 方法中,绝对不要在 finally 块里写 return。否则,事务的异常感知机制会被破坏。
正确的处理方式
java
@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 的返回值吗?为什么?
java
public static int test() {
int flag = 10;
try {
flag = 20;
return flag;
} finally {
flag = 30;
return flag;
}
}
欢迎在评论区留下你的答案和分析。