You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Spring Boot Maven Plugin无法访问Jar包内资源的问题

问题描述

我使用的某库将模板文件存储在resources/vc-templates/*.json路径下,通过以下代码访问模板:

checkNotNull(resource) { "Cannot find resource path: $resourcePath" }

log.debug { "Loading templates from: $resource" }

when {
    File(resource.file).isDirectory ->
        File(resource.file).walk().filter { it.isFile }.map {
            (it.relativeTo(File(resource.file)).parent?.let { "$it/" } ?: "") + it.nameWithoutExtension
        }.toList()

    else -> {
        FileSystems.newFileSystem(resource.toURI(), emptyMap<String, String>()).use { fs ->
            Files.walk(fs.getPath(resourcePath))
                .filter { it.isRegularFile() }
                .map {
                    (Path.of(it.toString()).relativeTo(Path.of(resourcePath)).parent?.let { "$it/" } ?: "") + it.nameWithoutExtension
                }.toList()
        }
    }
}

本地执行./mvnw spring-boot:run运行一切正常,但执行./mvnw clean package打包成Jar部署时,出现找不到/vc-templates文件夹的错误,报错信息如下:

Caused by: java.nio.file.NoSuchFileException: /vc-templates
        at jdk.zipfs/jdk.nio.zipfs.ZipPath.readAttributes(ZipPath.java:769) ~[jdk.zipfs:na]
        at jdk.zipfs/jdk.nio.zipfs.ZipPath.readAttributes(ZipPath.java:777) ~[jdk.zipfs:na]
        at jdk.zipfs/jdk.nio.zipfs.ZipFileSystemProvider.readAttributes(ZipFileSystemProvider.java:276) ~[jdk.zipfs:na]
        at java.base/java.nio.file.Files.readAttributes(Files.java:1851) ~[na:na]
        at java.base/java.nio.file.FileTreeWalker.getAttributes(FileTreeWalker.java:220) ~[na:na]
        at java.base/java.nio.file.FileTreeWalker.visit(FileTreeWalker.java:277) ~[na:na]
        at java.base/java.nio.file.FileTreeWalker.walk(FileTreeWalker.java:323) ~[na:na]
        at java.base/java.nio.file.FileTreeIterator.<init>(FileTreeIterator.java:71) ~[na:na]
        at java.base/java.nio.file.Files.walk(Files.java:3918) ~[na:na]
        at java.base/java.nio.file.Files.walk(Files.java:3973) ~[na:na]
        at id.walt.credentials.w3c.templates.VcTemplateService$resourceWalk$1.invoke(VcTemplateService.kt:124) ~[waltid-ssikit-1.2306281832.0.jar!/:na]
        at id.walt.credentials.w3c.templates.VcTemplateService$resourceWalk$1.invoke(VcTemplateService.kt:109) ~[waltid-ssikit-1.2306281832.0.jar!/:na]
        at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74) ~[kotlin-stdlib-1.8.22.jar!/:1.8.22-release-407(1.8.22)]
        at id.walt.credentials.w3c.templates.VcTemplateService.listResources(VcTemplateService.kt:134) ~[waltid-ssikit-1.2306281832.0.jar!/:na]
        at id.walt.credentials.w3c.templates.VcTemplateService.listTemplates(VcTemplateService.kt:149) ~[waltid-ssikit-1.2306281832.0.jar!/:na]

代码日志输出:Loading templates from: jar:file:/Path/to/*.jar!/BOOT-INF/classes!/vc-templates,我疑惑classes后的感叹号是否导致文件夹无法被找到?有没有解决办法?另外想了解javaClass.getResource()在Jar文件中的工作机制。此外,打包生成的*.jar.original执行时提示:no main manifest attribute, in *.jar.original。


问题解答

1. classes后的感叹号不是问题根源

感叹号是Jar URL的标准格式,用于分隔Jar文件路径和内部资源路径。Spring Boot打包的Jar是嵌套结构,BOOT-INF/classes是应用代码和资源的存放目录,因此出现两个!是正常的,不会导致资源找不到。

2. 找不到vc-templates的解决办法

问题出在Jar模式下的路径处理:代码中直接用fs.getPath(resourcePath),但Spring Boot打包后资源实际位于BOOT-INF/classes/vc-templates,而不是Jar根目录的/vc-templates,导致路径匹配失败。

方案一:用ClassLoader枚举Jar内资源

checkNotNull(resource) { "Cannot find resource path: $resourcePath" }

log.debug { "Loading templates from: $resource" }

val templateNames = mutableListOf<String>()
when {
    File(resource.file).isDirectory -> {
        // 保留本地运行时的原有逻辑
        File(resource.file).walk().filter { it.isFile }.map {
            (it.relativeTo(File(resource.file)).parent?.let { "$it/" } ?: "") + it.nameWithoutExtension
        }.toList()
    }
    else -> {
        val classLoader = javaClass.classLoader
        val targetPath = resourcePath.removePrefix("/")
        val jarUri = resource.toURI()
        if (jarUri.scheme == "jar") {
            // 提取Jar文件路径
            val jarPath = jarUri.path.split("!")[0].substring(5)
            JarFile(jarPath).use { jarFile ->
                jarFile.entries().asSequence()
                    .filter { !it.isDirectory && it.name.startsWith(targetPath) }
                    .forEach { entry ->
                        // 计算相对路径与模板名称
                        val relativeEntryPath = entry.name.substring(targetPath.length + 1)
                        val name = if (relativeEntryPath.contains("/")) {
                            relativeEntryPath.substringBeforeLast("/") + "/" + entry.name.substringAfterLast("/").substringBeforeLast(".")
                        } else {
                            entry.name.substringAfterLast("/").substringBeforeLast(".")
                        }
                        templateNames.add(name)
                    }
            }
        }
        templateNames
    }
}

方案二:用Spring内置工具简化处理(推荐)

如果是Spring Boot项目,直接用PathMatchingResourcePatternResolver自动处理本地/Jar资源:

val resolver = PathMatchingResourcePatternResolver()
val resources = resolver.getResources("classpath:vc-templates/**/*.json")
val templateNames = resources.map { resource ->
    val path = resource.url.path
    val relativePath = path.substring(path.indexOf("vc-templates/") + "vc-templates/".length)
    relativePath.substringBeforeLast(".json")
}

3. javaClass.getResource()在Jar中的工作机制

  • 本地运行时:资源是文件系统中的真实文件/目录,getResource()返回file:协议URL,可直接用File类访问。
  • Jar运行时:资源是Jar内部的条目,返回jar:协议URL(格式为jar:file:/xxx.jar!/内部路径),无法直接用File访问,必须通过JarFile或ZipFileSystem读取。
  • 加载逻辑:getResource()通过当前类的ClassLoader从类路径查找资源,Spring Boot打包后类路径包含BOOT-INF/classes和BOOT-INF/lib下的依赖Jar,资源会从这些位置检索。

4. *.jar.original无法运行的原因

*.jar.original是Maven打包时生成的原始Jar文件,仅包含项目编译后的class和资源,没有Spring Boot的启动配置(如MANIFEST.MF中的Main-Class属性)和依赖包,因此无法直接运行。正常运行应使用不带.original后缀的Spring Boot可执行Jar,该Jar包含所有依赖和启动逻辑。


内容的提问来源于stack exchange,提问作者cheekz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 00:13:12