開發階段頻繁修改類別,重新打包整個 JAR 耗時。若專案採標準 Maven 結構,可編譯後直接用 jar uf 指令增量更新 JAR 內的 com/ 目錄,秒級完成熱部署。
project-root/
├── src/
├── target/ # 編譯輸出 classes
├── test.jar # 需更新的目標 JAR
└── jarUpdaterConfig.properties
package com.foo.common.base.utils.development;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JarUpdater {
private static final Logger log = LoggerFactory.getLogger(JarUpdater.class);
@Test
public void updateClass() throws IOException, InterruptedException {
// 1. 讀取設定檔
Properties p = new Properties();
p.load(new ClassPathResource("jarUpdaterConfig.properties").getInputStream());
// 2. 編譯最新類別(略,假設已由 IDE/建構工具產出至 target/)
new ClassUpdater().applySettings(p).compileAndCopyClass();
String workDir = p.getProperty("workingDirectory");
String jarName = p.getProperty("jar4UpdateName");
File targetJar = new File(workDir, jarName);
if (!targetJar.isFile()) {
log.error("JAR 不存在: {}", jarName);
return;
}
long oldSize = targetJar.length();
log.info("準備更新 JAR: {}, 目前大小: {}", jarName, oldSize);
// 3. 執行 jar uf 命令
// jar uf -C <套件根目錄>
String cmd = String.format("jar uf %s%s -C %s %s",
workDir, jarName, workDir + "target", "com");
log.info("執行指令: {}", cmd);
Runtime.getRuntime().exec(cmd);
// 4. 等待檔案修改時間更新(簡易輪詢)
Date start = new Date();
while (!FileUtils.isFileNewer(targetJar, start)) {
log.info("等待 JAR 寫入完成...");
Thread.sleep(2000);
}
// 5. 驗證大小變化
assert oldSize != targetJar.length() : "JAR 大小未變更,更新可能失敗";
log.info("JAR 更新完成,新大小: {}", targetJar.length());
}
}
jar uf:update file,只替換/新增指定路徑下的類別,不重建整個 JAR。-C target com:切換到 target/ 目錄,將 com/ 遞歸加入 JAR,保持套件結構。FileUtils.isFileNewer:避免 exec 非同步導致後續斷言過早執行。assertNotEquals:確保 JAR 確實被修改。JAVA_HOME/bin 在 PATH 中,否則 jar 指令找不到。