Java SpringBoot Web服务打包Jar后无法读取application.properties文件的解决方案咨询
嘿,这个问题我之前帮不少开发者踩过坑,核心原因是你误解了Spring Boot打包成Jar后资源的存储方式:BOOT-INF/classes/application.properties 并不是文件系统上的真实物理路径,而是Jar压缩包内部的一个资源条目。FileInputStream 只能访问文件系统里的文件,根本没法直接读取Jar包内的资源,所以你硬编码路径再怎么调正斜杠反斜杠都没用。
下面给你两种解决方案,从最推荐的Spring原生方案,到适配你现有代码的修改方案:
方案1:用Spring Boot原生配置绑定(强烈推荐)
Spring Boot本身就提供了完善的配置加载机制,完全不需要自己写PropertiesLoader来手动读配置,既简洁又不容易出错。
方式1:@Value直接注入
直接在需要用到配置的类里注入值,或者单独写一个配置类:
import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component public class PriceUrlConfig { @Value("${real.time.dollar.price}") private String realTimeUrl; @Value("${history.dollar.price}") private String historyUrl; // Getter方法 public String getRealTimeUrl() { return realTimeUrl; } public String getHistoryUrl() { return historyUrl; } }
之后在需要用这些URL的地方,直接注入PriceUrlConfig就行——Spring会自动帮你加载配置,甚至还能处理不同环境的配置文件(比如dev/prod)。
方式2:@ConfigurationProperties批量绑定
如果配置项比较多,这种方式更清晰,还能自动完成类型转换:
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "dollar.price") public class PriceUrlConfig { private String realTime; private String history; // Getter和Setter方法 public String getRealTime() { return realTime; } public void setRealTime(String realTime) { this.realTime = realTime; } public String getHistory() { return history; } public void setHistory(String history) { this.history = history; } }
对应的application.properties要调整前缀:
dollar.price.real-time=https://your-real-time-url.com dollar.price.history=https://your-history-url.com
这种方式完全符合Spring Boot的最佳实践,后续维护起来也更方便。
方案2:修改自定义PropertiesLoader,用类路径读取资源
如果你因为某些原因一定要保留自己的PropertiesLoader类,那得把文件读取方式改成类路径资源读取,因为类加载器可以访问Jar包内的资源。
修改后的代码如下:
import java.io.InputStream; import java.util.Properties; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public abstract class PropertiesLoader { private static final Logger LOG = LogManager.getLogger(PropertiesLoader.class); private static final String PROPERTIES_REAL_TIME_URL = "real.time.dollar.price"; private static final String PROPERTIES_HISTORY_URL = "history.dollar.price"; // 注意:直接用类路径根下的application.properties,不需要写BOOT-INF/classes private static final String PROPERTIES_PATH = "/application.properties"; private static String realTimeUrl; private static String historyUrl; private PropertiesLoader() { } public static void load() { Properties props = new Properties(); // 用类加载器获取资源流,支持读取Jar内的资源 try (InputStream is = PropertiesLoader.class.getResourceAsStream(PROPERTIES_PATH)) { if (is == null) { throw new IllegalArgumentException("找不到类路径下的application.properties文件"); } props.load(is); realTimeUrl = props.getProperty(PROPERTIES_REAL_TIME_URL); historyUrl = props.getProperty(PROPERTIES_HISTORY_URL); } catch (Exception e) { LOG.error(e.getMessage(), e); System.exit(-1); } } public static String getRealTimeUrl() { return realTimeUrl; } public static String getHistoryUrl() { return historyUrl; } }
关键修改点:
- 路径简化成
/application.properties:因为application.properties默认就在类路径根目录(BOOT-INF/classes就是类路径的一部分),不需要写完整的Jar内部路径。 - 用
getResourceAsStream()替代FileInputStream:类加载器的这个方法可以读取类路径下的所有资源,包括Jar包内的。 - 用try-with-resources语法自动关闭流,避免资源泄漏。
最后提醒你:如果用方案2,最好确保PropertiesLoader.load()在Spring上下文初始化之后调用,或者保证类加载器能正确找到资源。不过还是优先推荐方案1,毕竟Spring已经帮你处理了所有复杂的加载逻辑。
内容的提问来源于stack exchange,提问作者Leonardo Manzini

