Java 应用打包为 JAR 后执行外部脚本及读写资源文件的完整实践指南

大明姑娘_4832

大明姑娘_4832

2026-07-05

915人浏览

原创

Java 应用打包成 JAR 后,无法直接通过文件路径访问 src/main/resources 中的脚本或配置文件,因为 JAR 是只读 ZIP 归档;正确做法是:运行时从 JAR 中提取脚本到临时目录执行,并将可变数据(如 token、JSON、文本)写入外部可写路径(如用户目录或系统临时目录)。

java 应用打包成 jar 后,无法直接通过文件路径访问 `src/main/resources` 中的脚本或配置文件,因为 jar 是只读 zip 归档;正确做法是:**运行时从 jar 中提取脚本到临时目录执行,并将可变数据(如 token、json、文本)写入外部可写路径(如用户目录或系统临时目录)**。

在 Java 项目中,将脚本(如 .bat 或 .sh)与配套资源(文本、JSON 文件)一同放在 src/main/resources 下,本地开发时可通过相对路径顺利调用——但这依赖 IDE 直接映射文件系统的真实路径。一旦打包为 JAR,所有资源均以 ZIP 条目形式压缩存于归档内,不再是操作系统可识别的“文件”,因此 new ProcessBuilder("/src/main/resources/hello.bat") 必然失败:路径 /src/main/resources/ 在部署环境中根本不存在,且 Windows/Linux 的命令解释器(cmd.exe/bash)完全无法解析 jar:file://...!/hello.bat 这类 URI。

✅ 正确方案:分离“只读资源”与“可写数据”

1. 从 JAR 中安全提取并执行脚本

脚本本身属于只读逻辑资产,应保留在 JAR 内部(便于版本控制与分发),但需在运行时解压至真实文件系统供操作系统调用:

import java.io.IOException;
import java.nio.file.*;
import java.util.concurrent.TimeUnit;

public void runScript4() {
    String scriptName = "hello.bat"; // 确保该文件已打包进 JAR 的 classpath 根目录(即 resources/ 下)

    try {
        // 步骤1:从 JAR 中读取脚本字节流
        try (var inputStream = getClass().getClassLoader().getResourceAsStream(scriptName)) {
            if (inputStream == null) {
                throw new IOException("脚本未找到: " + scriptName + " —— 请确认文件位于 src/main/resources/ 且已正确打包");
            }

            // 步骤2:创建临时 .bat 文件(自动清理策略可选)
            Path tempScript = Files.createTempFile("exec-", ".bat");
            Files.copy(inputStream, tempScript, StandardCopyOption.REPLACE_EXISTING);
            tempScript.toFile().setExecutable(true); // Linux/macOS 需设执行权限

            // 步骤3:构造 ProcessBuilder,指向真实临时文件
            ProcessBuilder pb = new ProcessBuilder(tempScript.toString());

            // ⚠️ 关键:脚本若需读写外部文件,必须显式指定工作目录和 I/O 重定向
            pb.directory(getWorkingDir()); // 见下文
            pb.redirectErrorStream(true);

            Process process = pb.start();
            boolean finished = process.waitFor(30, TimeUnit.SECONDS);

            if (!finished) {
                process.destroyForcibly();
                throw new RuntimeException("脚本执行超时(30秒)");
            }

            // 读取输出
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream(), "UTF-8"))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            }
        }
    } catch (IOException | InterruptedException e) {
        e.printStackTrace();
    }
}

? 注意事项:

  • 脚本路径 hello.bat 必须不带前导 /(即使用 getResourceAsStream("hello.bat")),表示从 classpath 根查找;若放在子目录(如 scripts/hello.bat),则路径为 "scripts/hello.bat"。
  • Files.createTempFile() 创建的文件默认在系统临时目录(如 C:\Users\XXX\AppData\Local\Temp\ 或 /tmp/),JVM 退出后可能被自动清理,适合一次性任务;若需长期保留,请改用用户目录(见下文)。
  • Windows 下 .bat 文件无需 setExecutable(true);Linux/macOS 的 .sh 脚本需此调用。

2. 为脚本提供可写的输入/输出文件路径

脚本依赖的 token 文件、text.txt 和 config.json 属于运行时可变数据,绝不可写入 JAR 内部(JAR 是只读的)。应统一存放于外部持久化目录:

// 推荐:用户主目录下的应用专属子目录(跨平台、持久、用户可见)
private static final Path DATA_DIR = Paths.get(System.getProperty("user.home"), ".myapp");

static {
    try {
        Files.createDirectories(DATA_DIR);
    } catch (IOException e) {
        throw new RuntimeException("无法创建数据目录: " + DATA_DIR, e);
    }
}

// 获取可写文件路径示例
public Path getTokenFile(String name) {
    return DATA_DIR.resolve(name + ".token");
}

public Path getTextFile() {
    return DATA_DIR.resolve("input.txt");
}

public Path getJsonFile() {
    return DATA_DIR.resolve("config.json");
}

然后,在调用 ProcessBuilder 时,显式传入这些真实路径作为脚本参数或环境变量:

Java Maven Code Review
Java Maven Code Review

审查Java Maven项目(ZIP压缩包或GitLab仓库URL),检查代码规范、命名、模块边界、可维护性问题以及重复代码。

下载
pb.command(tempScript.toString(), 
           getTextFile().toString(), 
           getJsonFile().toString(),
           getTokenFile("auth").toString());

脚本内部即可通过命令行参数接收路径,安全读写——彻底规避 JAR 内部写入限制。

3. 替代方案:纯 Java 实现(更健壮、免依赖外部脚本)

对于简单任务(如生成 token、修改 JSON),强烈建议用 Java 原生能力替代外部脚本:

// 示例:用 Java 生成 token 并写入 JSON
private void generateAndPersistToken() throws IOException {
    String token1 = UUID.randomUUID().toString();
    String token2 = UUID.randomUUID().toString();

    // 读取原始文本
    String inputText = Files.readString(getTextFile(), StandardCharsets.UTF_8);

    // 构建 JSON(使用 Jackson 或 org.json)
    JsonObject json = Json.createObjectBuilder()
            .add("token1", token1)
            .add("token2", token2)
            .add("source", inputText)
            .build();

    // 写入 JSON 文件(自动创建父目录)
    Files.writeString(getJsonFile(), json.toString(), 
                      StandardCharsets.UTF_8, 
                      StandardOpenOption.CREATE, 
                      StandardOpenOption.WRITE);
}

✅ 优势:

  • 无平台差异(无需 .bat/.sh 分支);
  • 无进程启动开销与安全风险(如注入攻击);
  • 可精确控制异常、日志与事务性(如原子写入);
  • 完全兼容 JAR 部署场景。

总结

场景 正确做法
读取 JAR 内资源 用 Class.getResourceAsStream() + InputStream,禁止转 File 或 Path
执行外部脚本 先 getResourceAsStream() 提取到临时文件,再 ProcessBuilder 执行
写入运行时数据 固定使用外部路径:user.home/.app(持久) 或 System.getProperty("java.io.tmpdir")(临时)
避免陷阱 删除所有 new File("src/main/resources/...")、ResourceUtils.getFile("classpath:...") 等无效代码

遵循以上原则,即可确保 Java 应用在 IDE 开发、Maven 打包、Docker 容器或任意服务器环境中,均能稳定读取嵌入资源、安全执行脚本、可靠持久化数据。

Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

java

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

2023.06.15

9997

6

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

2023.07.05

7142

9

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

2023.07.31

6272

8

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.01

1084

3

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.02

908

3

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

1316

5

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

2629

5

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

2023.08.03

19951

3

配置java环境变量
配置java环境变量

配置Java环境变量是为了让操作系统能够识别和使用Java的相关命令和功能。本专题为大家提供配置java环境变量相关文章,帮助大家解决问题。

2023.08.03

1175

8

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习