写一个正在运行的jar子

我正在尝试创建一个Jar文件,其中包含Jar内部的数据,这些数据程序的执行之间持续存在。 我知道还有其他方法可以保存数据,但我想要一个完全独立的Jar文件。

我有一些似乎有用的东西,我希望对你在我的方法中看到的任何漏洞提出反馈,因为有些东西让人感觉很乱。 我正在做的是这样的:

  • Jar包含一个文件saves.txt。 该文件包含一个数字:在开头,它是1。
  • 运行Jar时,它会读取此文件(使用getResourceAsStream)并将该数字显示给用户。
  • 然后,我使用ZipFile API将整个jar解压缩到一个临时目录。
  • 既然saved.txt是一个文件(不是资源),我递增数字并覆盖临时目录中的文件。
  • 然后我使用JarOutputStream将临时目录(包括更新的saves.txt文件)的内容重新压缩回同一个jar
  • 最后,我删除临时目录并退出程序。 当用户再次运行jar时,saved.txt文件已更新。

我正在做所有这些麻烦来解决这个bug,你不能只更新jar中的单个文件: http : //bugs.java.com/bugdatabase/view_bug.do? video_id = 4129445

而且我没有使用jar -u命令,因为我不想要求最终用户拥有JDK。

我可以看到这方面的一些问题,但它们都不是我的交易破坏者:

  • 这不适用于签名的jar子。 这对我来说不是问题,因为无论如何都不会签署这些jar子。
  • 如果jar在没有写权限的目录中,这将不起作用。 我可以向用户发出警告。
  • 如果在程序运行时jar在档案资源管理器(如7zip)中打开,则无法使用此function。 我可以通过向用户显示要求他们关闭它的消息来解决这个问题。
  • 这使得部署新版本的jar很烦人,因为保存在jar中。 用户将不得不以某种方式从旧jar加载数据,或者开始清理。 我也没关系。

所以,我的问题是: 上面的任何一个看起来特别可怕吗? 如果是这样,为什么?

编辑:我应该注意,我并不是在问这个方法是标准的还是混乱的,我在询问代码 。 有什么理由代码不起作用吗?

“在jar子运行时重写jar的内容”步骤让我最紧张,但我找不到任何文档说你不能,甚至不应该这样做。 如果我在程序中间执行此操作,那么可能会发生不好的事情,但这只会发生在程序的最后。 覆盖jar是程序的最后一行。

最重要的是,这一切似乎都有效! 我在Windows 7,Windows 8和Mac上测试了这个。 因此,如果有人知道任何文档,或者可以想到这个代码不起作用的极端情况,我肯定会感激它。

这是一张显示以上所有内容的MCVE。 为此,您必须将此类编译为可运行的jar文件以及包含单个数字的saves.txt文件。

import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.jar.JarOutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.swing.JOptionPane; public class JarTest { static List extractedFiles = new ArrayList(); public static void unzipWholeJar() throws IOException{ File jarFile = new File(JarTest.class.getProtectionDomain().getCodeSource().getLocation().getPath()); ZipFile jarZipFile = new ZipFile(jarFile); Enumeration entities = jarZipFile.entries(); while (entities.hasMoreElements()) { ZipEntry entry = (ZipEntry)entities.nextElement(); if(!entry.isDirectory()){ InputStream in = jarZipFile.getInputStream(jarZipFile.getEntry(entry.getName())); File file = new File("extracted/" + entry.getName()); extractedFiles.add(file); File parent = new File(file.getParent()); parent.mkdirs(); OutputStream out = new FileOutputStream(file); byte[] buffer = new byte[65536]; int bufferSize; while ((bufferSize = in.read(buffer, 0, buffer.length)) != -1){ out.write(buffer, 0, bufferSize); } in.close(); out.close(); } } jarZipFile.close(); } public static void rezipExtractedFiles() throws FileNotFoundException, IOException{ File jarFile = new File(JarTest.class.getProtectionDomain().getCodeSource().getLocation().getPath()); JarOutputStream jos = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(jarFile))); for(File f : extractedFiles){ String absPath = f.getAbsolutePath().replaceAll("\\\\", "/"); String fileInJar = absPath.substring(absPath.indexOf("extracted") + 10); addFile(jos, fileInJar, f); } jos.close(); } public static void addFile(JarOutputStream jos, String fileInJar, File file) throws FileNotFoundException, IOException{ InputStream in = new FileInputStream(file); jos.putNextEntry(new ZipEntry(fileInJar)); int bufferSize; byte[] buffer = new byte[4096]; while ((bufferSize = in.read(buffer, 0, buffer.length)) != -1) { jos.write(buffer, 0, bufferSize); } in.close(); jos.closeEntry(); } public static void updateSavesFile(int newX) throws IOException{ BufferedWriter writer = new BufferedWriter(new FileWriter("extracted/saves.txt")); writer.write(String.valueOf(newX)); writer.close(); } public static void deleteExtracted(File f){ if (f.isDirectory()) { for (File c : f.listFiles()) deleteExtracted(c); } f.delete(); } public static void main(String... args) throws IOException{ //read saves file in BufferedReader br = new BufferedReader(new InputStreamReader(JarTest.class.getClassLoader().getResourceAsStream("saves.txt"))); String line = br.readLine(); int x = Integer.parseInt(line); br.close(); //show the message JOptionPane.showMessageDialog(null, "Times opened: " + x); //unzip the whole jar //this will fail if the jar is in a folder without write privileges //but I can catch that and warn the user unzipWholeJar(); //update the unzipped saves file updateSavesFile(x+1); //put the files back into the jar //this will fail if the jar is open in an archive explorer //but I can catch that and warn the user rezipExtractedFiles(); //delete what we extracted deleteExtracted(new File("extracted")); } } 

概念

这有点可怕。

  • 与使用jar外部的配置文件相比,这很浪费。
  • 它并没有真正增加价值。 用户通常不关心配置文件的位置。
  • 程序中保存状态。 为什么这么糟糕? 由于它们没有分离,因此用户无法在不复制整个程序的情况下轻松传输配置,或者在不共享其配置的情况下共享程序。
  • 如果您可以读取和重写jar,也可以将配置文件写入同一目录。 通常。
  • 假设它确实可以正常工作,它会使您的代码复杂化,可能会造成不必要的麻烦。
  • 如果它不起作用,您的用户可能会丢失可执行文件。 不是一个好的用户体验。

你不需要把它放在同一个jar子里。 能够将与配置捆绑在一起的jar快照导出到程序中可能是一项function,但默认情况下它可能没有足够的价值。

实施

主机操作系统资源不足时会发生什么? 文件描述符,空格……你可能在java.io.File.得到一个NullPointerException ,但是你在硬盘上留下了什么状态? 您的程序可以处理现有文件吗? 所有这些都是不必要的复杂性,您可以通过使用jar之外的文件避免。

由于外部原因,写作可能随时失败。 由于您不使用finally块来关闭流,因此我不确定是否会刷新更改。

结论

面对你在Processing中遇到的持久性问题,这是一个聪明的黑客,但仍然是一个黑客。 它会在某个时候爆炸。 如果可以避免,那就去做吧。 另一方面,我可能会误解Processing的问题,因此可能有更合适的解决方案。

真是一个奇怪的解决方案。 为什么不使用.jar外的文件来存储数据?

我在您的解决方案中看到的一些问题

  • 复杂。 目前尚不清楚为什么所有这些额外复杂性的优势与将数据存储在文件中相比。

  • 版本控制:当您需要在.jar中向您的客户端分发新版本的程序时,您将覆盖.jar!中的数据。 您需要在复杂的程序更新机制中思考,再次是不合理的复杂性。

是的,它有点可怕。

您可以将zip文件系统用于URL“jar:file:/ …”。 然后你只需操作zip(jar)中的单个“文件”。

我不确定,有人可能会更改正在运行的jar。 在Linux上可能是的。

 Map zipProperties = new HashMap<>(); // zipProperties.put("create", "true"); zipProperties.put("encoding", "UTF-8"); try (FileSystem zipFS = FileSystems.newFileSystem(jarUri, zipProperties)) { Path dataPath = zipFS.getPath("/data/file.txt"); ... Files.copy(inputStream, dataPath, StandardCopyOption.REPLACE_EXISTING); }