我的情况:
使用Java 17.0.8
我有一个应用程序 A.jar,它在启动时检查更新,下载更新,然后运行 B.jar 以用新下载的应用程序文件替换应用程序文件。
问题:
从A开始,当我运行B应用程序然后运行
exit(0)
时,A应用程序应该在JVM关闭后释放对A.jar文件的锁定,这样B应用程序就可以用新的更新版本替换这些文件。
相反,A.jar 文件保持锁定状态,B 应用程序无法使用更新覆盖 A 文件。
相反,如果 A 应用程序退出,然后我手动启动 B 应用程序,它就会按预期工作。 我还尝试运行 B 应用程序,使用
cmd /c
nohup 启动它,但它仍然没有释放 A 文件上的锁定...
调试:
创建一个新项目并编译此代码三次,一次用于 A.jar,一次用于 B.jar,最后一次用于 A2.jar,然后运行 A.jar,然后按预期成功更新到 A2.jar 版本.
public class Main implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Main());
}
@Override
public void run() {
String x = "A"; //"A" for A.jar, "B" for B.jar and "A2" for A2.jar
File dir = new File(".").getAbsoluteFile().getParentFile();
File A = new File(dir.getAbsolutePath() + File.separator + "A.jar");
File B = new File(dir.getAbsolutePath() + File.separator + "B.jar");
File A2 = new File(dir.getAbsolutePath() + File.separator + "A2.jar");
switch (x) {
case "A" -> {
JOptionPane.showMessageDialog(null, x + ": Lancio Aggiornamento!");
try {
Runtime.getRuntime().exec("cmd /c java -jar \"" + B.getAbsolutePath() + "\"");
System.exit(0);
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, x + ": Aggiornamento Fallito!");
}
}
case "B" -> {
try {
JOptionPane.showMessageDialog(null, x + ": Sto Aggiornando!");
Files.copy(A2.toPath(), A.toPath(), StandardCopyOption.REPLACE_EXISTING);
A2.delete();
Runtime.getRuntime().exec("cmd /c java -jar \"" + A.getAbsolutePath() + "\"");
System.exit(0);
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, x + ": Aggiornamento Fallito!");
}
}
case "A2" -> {
B.delete();
JOptionPane.showMessageDialog(null, x + ": Sono Aggiornamento!");
}
default -> { }
}
}
}
所以我想这是我的项目的问题。问题是有太多的代码和库来尝试找到罪魁祸首......
问题:
在我退出 JVM 并且它完全关闭,甚至从任务管理器中消失后,什么可能会导致主 .jar 文件保持锁定状态?难道它不应该释放 .jar 文件上的任何剩余锁定吗?
我知道如果我没有正确关闭文件流,那么它可能无法完全释放锁定,但我不会直接对 jar 文件本身执行任何操作...
好吧,这很奇怪......
我克隆了应用程序项目,并开始删除一点,直到只剩下这个:
public class Main implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Main());
}
@Override
public void run() {
String dir = new File(".").getAbsoluteFile().getParentFile().getAbsolutePath() + File.separator;
try {
Runtime.getRuntime().exec("cmd /c java -jar \"" + dir + "Update" + File.separator + "Updater.jar\"");
} catch (IOException ex) { }
System.exit(0);
}
}
在这一点上,毫无疑问,问题不是应用程序,而是更新程序本身......所以我也继续克隆该项目,并开始对其进行逆向工程,直到我终于明白问题出在哪里了。
事情是这样的:
我的基本文件结构如下:
App Folder
├─ App.jar
├─ Update
│ ├─ Updater.jar
│ ├─ Data
│ │ ├─ NewApp.jar
App.jar
启动 Updater.jar
并退出。
Updater.jar
尝试找出它自己的位置路径,然后能够找到父级App Folder
的路径和子级Data
文件夹的路径。
要查找
.jar
当前目录,我可以通过执行 new File(".").getAbsoluteFile().getParentFile();
来获取它。这将生成一个“.”文件对象与正在运行的应用程序的 .jar
文件位于同一目录中。通过获取该对象的父对象,我基本上获得了当前应用程序的目录路径。 这就是打破!
如果我手动打开
Updater.jar
,路径是正确的,没有问题。
但是如果是
App.jar
打开了Updater.jar
,那么出于某种原因,Updater应用调用的new File(".")
将不再在Updater.jar
目录中创建,而是在Updater所在的App.jar
目录中创建被处决。
为了解决这个问题,我暂时用
new File(context.getProtectionDomain().getCodeSource().getLocation().toURI())
替换了有问题的代码,以获取当前的应用程序目录,它工作正常,但我必须找到一种“正确”的方法,并真正更好地研究它,因为我开始也对这个解决方案产生怀疑!
老实说,这对我来说没有任何意义,我觉得这更像是一个错误而不是一个功能......但尽管如此,我终于弄清楚了它的真相,现在一切都按预期工作了!