Tomcat 11.0.25 的嵌套 JSP 报 Zip file closed,11.0.27 已修正
嵌套 JAR 中的 JSP 报 Zip file closed 时,检查 Tomcat 11.0.25/26 关闭共享 JarFile 的回归;11.0.27 已修正,临时缓存设置需评估进程范围影响。
可执行 WAR 能启动,访问位于依赖 JAR 中的 JSP 时却失败,堆栈从 Jasper 一直落到 Boot loader:
java.lang.IllegalStateException: Zip file closed
org.springframework.boot.loader.jar.NestedJarFile.ensureOpen
org.apache.jasper.JspCompilationContext.getResourceAsStream
这条回归在 Spring Boot 4.1.1 手动采用 Tomcat Embed 11.0.25 的组合中有明确表现,11.0.26 仍有反馈。Tomcat 11.0.27 的正式变更记录包含修正:只有能确认 JAR 不共享时,才允许相关 URL 连接关闭它。
先确认是共享 JAR 被关闭
受影响的 Tomcat 代码使用 CloseableURLConnection 读取资源,关闭时会释放 JarURLConnection 返回的 JarFile。但这里又经过缓存资源包装,内部另开连接时没有延续禁用缓存的设置,取得的仍可能是共享的嵌套 JAR。
某次资源读取把它关掉后,其他用户继续读同一个归档就会报 Zip file closed。它不意味着磁盘里的 WAR 已经损坏,也与读压缩内容时的 ZLIB 提前结束 不同。
记录完整堆栈、WAR 打包方式和真正加载的 Tomcat 版本;开发环境直接读普通文件的 JSP,可能不经过同一条路径。若没有 NestedJarFile、资源也不在嵌套归档中,应继续检查谁关闭了那个实际归档,不能直接套用此结论。
更新整组 Tomcat Embed 依赖
在兼容项目当前 Boot 版本的前提下,使用包含修正的 Tomcat 11.0.27 或后续合适的维护版本。tomcat-embed-core、tomcat-embed-jasper 及其他相关组件要保持匹配,不能只在服务器目录替换某一个 JAR。
使用 Boot Maven parent 且由 tomcat.version 管理时,可按项目已有方式调整该属性;导入 BOM、Gradle 或自定义依赖管理的项目应改其真实版本来源。不要新加一个无效的属性后,只凭构建没有报错便认为已更新。
修正后重新打包可执行 WAR,核对最终依赖,再从原启动方式访问原 JSP。验收需要覆盖首次编译、多次访问及其他位于同一嵌套归档的资源;只检查应用启动没有异常,触发不到这条资源读取路径。
暂时无法更新时,禁用 jar URL 缓存有代价
对于已经确认的共享归档关闭路径,可评估在应用启动前禁用 jar 协议的默认连接缓存:
import java.net.URLConnection;
public static void main(String[] args) {
URLConnection.setDefaultUseCaches("jar", false);
SpringApplication.run(Application.class, args);
}
这是现有 Boot 主类的片段,Application 换成项目实际主类,保留 SpringApplication 的原导入与其他初始化。设置必须早于相关资源连接创建,不能等 JSP 报错后再调用。
它影响进程中随后创建的相关 JAR URL 连接,不只当前一个 JSP,可能改变资源读取开销。它也不能重新打开已经被关闭的共享对象,所以应通过正常应用重启使新设置生效,而不是在运行中反复补救。
优先采用正式修正。临时设置需要覆盖归档读取与 JSP 功能检查,升级后评估撤除。不要退回已知缺少其他安全修正的 Tomcat 版本,也不要捕获 Zip file closed 后直接返回空白页面;后者只会掩盖资源读取失败。


