Spring Boot 3.2升级时依赖出现FileSystemNotFoundException求助
Spring Boot 3.2.0升级后picovoice-java的requiresUnpack配置失效导致FileSystemNotFoundException
问题重现
升级Spring Boot从3.1.6到3.2.0后,启动时抛出以下异常:
java.lang.ExceptionInInitializerError: null at ai.picovoice.porcupine.Porcupine$Builder.build(Porcupine.java:242) ... Caused by: java.nio.file.FileSystemNotFoundException: null at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:156) at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.getPath(ZipFileSystemProvider.java:142) at java.base/java.nio.file.Path.of(Path.java:209) at java.base/java.nio.file.Paths.get(Paths.java:98) at ai.picovoice.porcupine.Utils.getResourceDirectory(Utils.java:58) at ai.picovoice.porcupine.Utils.<clinit>(Utils.java:39) ... 28 common frames omitted
Picovoice依赖中获取资源的核心逻辑为:
private static Path getResourceDirectory() throws RuntimeException { URL resourceURL = Porcupine.class.getProtectionDomain().getCodeSource().getLocation(); Path resourcePath; try { resourcePath = Paths.get(resourceURL.toURI()); } catch (URISyntaxException var4) { resourcePath = Paths.get(resourceURL.getPath()); } if (resourcePath.toString().endsWith(".jar")) { try { resourcePath = extractResources(resourcePath); } catch (IOException var3) { throw new RuntimeException("Failed to extract resources from Porcupine jar."); } } return resourcePath.resolve("porcupine"); }
原Spring Boot 3.1.6中生效的requiresUnpack配置:
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>net.indexoutofmj.neptune.NeptuneBot</mainClass> <requiresUnpack> <dependency> <groupId>ai.picovoice</groupId> <artifactId>picovoice-java</artifactId> </dependency> </requiresUnpack> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin>
原因分析
Spring Boot 3.2.0默认启用了分层打包机制,同时调整了requiresUnpack的处理逻辑:指定依赖的资源会被直接解压到主应用jar的BOOT-INF/classes目录下,而非保持独立jar的形式。
而Picovoice的资源获取逻辑依赖CodeSource.getLocation()获取自身jar路径,此时该路径指向的是主应用jar而非原picovoice-java jar。代码尝试从主jar中提取porcupine资源时,因主jar中并不包含该目录的完整结构(已被解压到类路径),触发FileSystemNotFoundException。
解决方案
方案1:禁用分层打包(快速恢复)
在spring-boot-maven-plugin配置中禁用分层打包,恢复到3.1.6的打包行为:
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>net.indexoutofmj.neptune.NeptuneBot</mainClass> <requiresUnpack> <dependency> <groupId>ai.picovoice</groupId> <artifactId>picovoice-java</artifactId> </dependency> </requiresUnpack> <!-- 禁用分层打包 --> <layers> <enabled>false</enabled> </layers> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin>
方案2:修改Picovoice资源获取逻辑(推荐适配)
调整Utils.getResourceDirectory()方法,优先从类加载器读取已解压的porcupine资源,避免依赖CodeSource路径:
private static Path getResourceDirectory() throws RuntimeException { // 优先从类路径获取porcupine目录 URL porcupineResource = Porcupine.class.getClassLoader().getResource("porcupine"); if (porcupineResource != null) { try { return Paths.get(porcupineResource.toURI()); } catch (URISyntaxException e) { logger.warning("Failed to resolve porcupine resource from classpath, falling back to jar extraction: " + e.getMessage()); } } // 原有jar提取逻辑 URL resourceURL = Porcupine.class.getProtectionDomain().getCodeSource().getLocation(); Path resourcePath; try { resourcePath = Paths.get(resourceURL.toURI()); } catch (URISyntaxException var4) { resourcePath = Paths.get(resourceURL.getPath()); } if (resourcePath.toString().endsWith(".jar")) { try { resourcePath = extractResources(resourcePath); } catch (IOException var3) { throw new RuntimeException("Failed to extract resources from Porcupine jar.", var3); } } return resourcePath.resolve("porcupine"); }
方案3:手动提取资源(临时 workaround)
在应用启动时手动将picovoice-java jar中的porcupine目录复制到临时目录,并指定给Picovoice使用:
// 在应用启动类main方法中添加 String tempDir = System.getProperty("java.io.tmpdir") + File.separator + "porcupine"; File porcupineDir = new File(tempDir); if (!porcupineDir.exists()) { try (JarFile jarFile = new JarFile(new File(Paths.get(Porcupine.class.getProtectionDomain().getCodeSource().getLocation()).toURI()))) { Enumeration<JarEntry> entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry.getName().startsWith("porcupine/")) { File destFile = new File(tempDir + File.separator + entry.getName()); if (entry.isDirectory()) { destFile.mkdirs(); } else { destFile.getParentFile().mkdirs(); try (InputStream is = jarFile.getInputStream(entry); OutputStream os = new FileOutputStream(destFile)) { byte[] buffer = new byte[1024]; int len; while ((len = is.read(buffer)) != -1) { os.write(buffer, 0, len); } } } } } } catch (Exception e) { throw new RuntimeException("Failed to extract porcupine resources manually", e); } } // 设置系统属性让Picovoice使用该目录 System.setProperty("ai.picovoice.porcupine.resource.dir", tempDir);
内容的提问来源于stack exchange,提问作者Jes Müller
相关产品推荐
相关产品推荐

