为什么Maven打包Jar时无法读取生成的MANIFEST.MF文件?
问题概述
你遇到的是Spring Boot可执行Jar包读取自定义MANIFEST.MF失败的问题,具体表现为:
- 用类加载器读取
META-INF/MANIFEST.MF只能拿到依赖包的清单,无法获取当前应用打包生成的清单 - 使用ServletContext读取同样返回null
- 手动将MANIFEST.MF放到
resources/META-INF/下可以正常读取 - 同配置的WAR包项目可以正常读取清单
问题原因
- Spring Boot 可执行Jar采用了独立的分层归档结构,maven-jar-plugin生成的
MANIFEST.MF默认存放在Jar包的根级META-INF/目录下,而Spring Boot内置的类加载器LaunchedURLClassLoader的类扫描范围仅包含BOOT-INF/classes/(应用编译类)和BOOT-INF/lib/(依赖Jar包),根级META-INF/不在类加载器的扫描路径内,所以你通过类加载器读取META-INF/MANIFEST.MF只能拿到依赖包的清单文件,拿不到当前应用的。 - 你本地开发阶段通过IDE直接运行项目时,运行的是
target/classes/下的编译文件,maven-jar-plugin的MANIFEST.MF是打包环节才生成的,不会出现在target/classes/META-INF/下,自然也读不到,你手动放到resources/META-INF/下的文件会被编译到该目录,所以可以正常读取。 - 内置ServletContext的资源扫描范围和类加载器对齐,仅覆盖
BOOT-INF/classes/、BOOT-INF/lib/下的静态资源路径,不会映射Jar包根目录的META-INF/,所以你用ServletContext读取也返回null。 - WAR包的场景是符合Servlet规范的标准归档结构,根级
META-INF/属于ServletContext的扫描范围,所以可以正常读取。 - 注意:你原配置中
<Custom-Entry>的闭合标签存在笔误,写为了</CustomEntry>,缺失横杠,会导致自定义字段无法正常生成到MANIFEST.MF中
解决方案
方案1(推荐,适配本地开发+Jar包运行全场景)
将生成的MANIFEST.MF输出到类路径下,适配类加载器的扫描规则:
- 在
src/main/resources/下创建空的META-INF/目录 - 调整maven-jar-plugin配置,新增outputDirectory配置将生成的清单文件输出到编译类路径:
<plugin> <artifactId>maven-jar-plugin</artifactId> <version>${maven.jar.version}</version> <configuration> <archive> <addMavenDescriptor>false</addMavenDescriptor> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <addDefaultImplementationEntries>true</addDefaultImplementationEntries> <packageName>${project.artifactId}-${project.version}</packageName> </manifest> <manifestEntries> <!-- 修正闭合标签的笔误 --> <Custom-Entry>someValue</Custom-Entry> </manifestEntries> </archive> <!-- 新增配置,将生成的MANIFEST.MF输出到编译类路径 --> <outputDirectory>${project.build.outputDirectory}</outputDirectory> </configuration> </plugin>
配置完成后,可直接通过类加载器读取:
InputStream is = this.getClass().getClassLoader().getResourceAsStream("META-INF/MANIFEST.MF");
方案2(仅适配Jar包运行场景)
直接读取当前运行的Jar包根目录的MANIFEST.MF,代码示例:
public Manifest getCurrentJarManifest() throws Exception { // 获取当前运行程序的代码源路径 File codeSource = new File(this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); // 仅Jar包运行时生效,IDE运行时返回classes目录,可自行添加兼容逻辑 if (codeSource.isFile() && codeSource.getName().endsWith(".jar")) { try (JarFile jarFile = new JarFile(codeSource)) { return jarFile.getManifest(); } } return null; }
内容的提问来源于stack exchange,提问作者Aleksey Gareev
相关产品推荐
相关产品推荐

