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

Spring Boot Web应用如何读取项目特定META/MANIFEST.MF文件

问题根因

原有代码无法正常读取MANIFEST.MF、始终返回null,核心原因是逻辑只适配了Spring Boot可执行jar包直接通过java -jar启动的场景,完全不兼容war包部署到外部Tomcat的运行模式:

  • 代码中通过类的ProtectionDomain拼接MANIFEST文件路径的逻辑,在Tomcat中拿到的是解压后WEB-INF/classes/目录的路径,而war包的MANIFEST.MF存放在war包根目录的META-INF/路径下,拼接出的目标路径本身就是错的
  • 用字符串完全相等的方式匹配MANIFEST文件URL的逻辑过于死板,Tomcat返回的资源URL会带有war:file:、!/等特殊前缀和分隔符,永远无法匹配到你拼接的路径字符串
  • 所有IO异常和读取异常都被静默吞掉,没有任何错误日志,完全无法定位具体失败点
  • 遍历类路径下所有MANIFEST.MF的逻辑会扫描到所有依赖jar包的清单文件,效率低还容易匹配错误目标
解决方案

你可以二选一,优先选Spring Boot官方提供的构建信息读取方案,兼容性最好,不需要自己处理文件读取逻辑。

方案1:使用Spring Boot官方BuildProperties(推荐)

这个方案不依赖手动读取MANIFEST.MF,Spring Boot会自动生成构建信息属性文件,jar、war部署场景都能兼容。

  1. 首先在Gradle配置中开启buildInfo任务,自动生成构建信息:
// build.gradle中添加配置
plugins {
    id 'org.springframework.boot' version '你当前使用的Spring Boot版本'
    // 其他原有插件
}

springBoot {
    buildInfo {
        // 自定义你需要的属性,比如构建号
        properties {
            buildNumber = project.findProperty("buildNum") ?: "dev"
            version = project.version
        }
    }
}
  1. 直接在Controller中注入BuildProperties即可使用,不需要自己定义Manifest的Bean:
import org.springframework.boot.info.BuildProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AppController
{    
    @Autowired
    private Environment env;
    
    @Autowired
    private BuildProperties buildProperties;
    
    @GetMapping("/get-app-details")
    public String getAppDetails()
    {
        String buildNum = buildProperties.getBuildNumber();
        // 兼容原有构建号截取逻辑
        if (buildNum != null && buildNum.contains("_")) {
            buildNum = buildNum.substring(buildNum.lastIndexOf('_') + 1);
        }
        String appVersion = env.getProperty("App.Version", "unknown") + "." + buildNum;
        return "Build Number - " + buildNum + ", AppVersion - " + appVersion;
    }
}

方案2:修正原有MANIFEST.MF读取逻辑,适配war部署

如果你必须读取MANIFEST.MF文件,不要用类路径拼接+遍历匹配的逻辑,直接通过ServletContext读取web应用根目录下的清单文件,兼容Tomcat等外部容器部署场景:

  1. 先确认Gradle的war任务配置正确,确保你需要的属性已经写入MANIFEST.MF:
war {
    manifest {
        attributes(
            'Build-Number': project.findProperty("buildNum") ?: "dev",
            'App-Version': project.version
        )
    }
}
  1. 修正AppConfig中的Manifest Bean定义:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.ServletContextAware;
import javax.servlet.ServletContext;
import java.io.InputStream;
import java.util.jar.Manifest;

@Configuration
public class AppConfig implements ServletContextAware {

    private ServletContext servletContext;

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    @Bean("manifest")
    public Manifest getManifest() {
        // War部署场景:通过ServletContext直接读取web应用根下的MANIFEST
        try (InputStream is = servletContext.getResourceAsStream("/META-INF/MANIFEST.MF")) {
            if (is != null) {
                return new Manifest(is);
            }
        } catch (Exception e) {
            // 本地开发运行场景:降级从类路径读取
            try (InputStream localIs = getClass().getClassLoader().getResourceAsStream("META-INF/MANIFEST.MF")) {
                if (localIs != null) {
                    return new Manifest(localIs);
                }
            } catch (Exception ex) {
                throw new RuntimeException("读取MANIFEST.MF失败", ex);
            }
        }
        throw new RuntimeException("未找到应用MANIFEST.MF文件,请检查构建配置");
    }
}
  1. 给Controller中的属性读取加非空判断,避免空指针:
@GetMapping("/get-app-details")
public String getAppDetails()
{
    Attributes mainAttributes = manifest.getMainAttributes();
    String buildNum = mainAttributes.getValue("Build-Number");
    if (buildNum == null) {
        buildNum = "unknown";
    } else if (buildNum.contains("_")) {
        buildNum = buildNum.substring(buildNum.lastIndexOf('_') + 1);
    }
    String appVersion = env.getProperty("App.Version", "unknown") + "." + buildNum;
    return "Build Number - " + buildNum + ", AppVersion - " + appVersion;
}
注意事项

不要在代码中静默吞掉所有异常,至少要打印错误日志,否则出现问题时完全没有排查线索。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 03:03:22