写一个正在运行的罐子

时间:2014-09-11 14:11:27

标签: java jar

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

我有一些似乎有用的东西,我希望对你在我的方法中看到的任何漏洞提出反馈,因为感觉对它有所了解。我正在做的是:

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

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

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

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

  • 这不适用于签名的jar。这对我来说不是问题,因为无论如何都不会签署这些罐子。
  • 如果jar位于没有写权限的目录中,则无效。我可以向用户发出警告。
  • 如果在程序运行时jar在档案资源管理器(如7zip)中打开,则无效。我可以通过向用户显示一条要求他们关闭它的消息来解决这个问题。
  • 这使得部署新版本的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<File> extractedFiles = new ArrayList<File>();

    public static void unzipWholeJar() throws IOException{

        File jarFile = new File(JarTest.class.getProtectionDomain().getCodeSource().getLocation().getPath());
        ZipFile jarZipFile = new ZipFile(jarFile);

        Enumeration<? extends ZipEntry> 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"));
    }
}

3 个答案:

答案 0 :(得分:3)

<强>概念

有点可怕。

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

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

<强>实施

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

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

<强>结论

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

答案 1 :(得分:1)

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

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

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

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

答案 2 :(得分:1)

是的,这有点可怕。

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

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

Map<String, String> 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);
}