写一个正在运行的罐子 [英] Writing to a Running Jar

查看:123
本文介绍了写一个正在运行的罐子的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to create a Jar file that contains data inside the Jar that persists between executions of the program. I know there are other ways to save data, but I'm going for a fully self-contained Jar file.

我有一些似乎有用的东西,我' d喜欢你在我的方法中看到的任何漏洞的反馈,因为感觉 hackish它。我正在做的是:

I have something that seems to work, and I'd like feedback on any holes you see in my approach, as something feels hackish about it. What I'm doing is this:


  • Jar包含一个文件saves.txt。此文件包含一个数字:在开头,它是1.

  • 当运行Jar时,它会读取此文件(使用getResourceAsStream)并将该数字显示给用户。

  • 然后我使用ZipFile API将整个jar解压缩到一个临时目录。

  • 既然saved.txt是一个文件(不是资源),我增加数字并覆盖临时目录中的文件。

  • 然后我使用JarOutputStream将临时目录的内容(包括更新的saves.txt文件)重新压缩回同一个jar

  • 最后,我删除临时目录并退出程序。当用户再次运行jar时,saved.txt文件已更新。

  • The Jar contains a file, saves.txt. This file contains a single digit: at the beginning, it's 1.
  • When the Jar is run, it reads this file (using getResourceAsStream) and displays that number to the user.
  • I then extract the whole jar to a temporary directory using the ZipFile API.
  • Now that saves.txt is a File (not a resource), I increment the number and overwrite the file in the temp directory.
  • Then I use the JarOutputStream to rezip the contents of the temporary directory (including the updated saves.txt file) back into the same jar.
  • Finally, I delete the temporary directory and the program exits. When the user runs the jar again, the saves.txt file has been updated.

我正在做所有这些琐事来绕过这个错误,您不能只更新jar中的单个文件: http: //bugs.java.com/bugdatabase/view_bug.do?bug_id=4129445

I'm doing all this rigmarole to get around this bug, where you can't just update a single file inside a jar: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4129445

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

And I'm not using the jar -u command because I don't want to require end users have the JDK.

我可以看到一些问题,但没有一个是我的交易破坏者:

I can see a few problems with this, but none of them are deal-breakers for me:


  • 这不适用于签名的罐子。这对我来说不是问题,因为无论如何这些罐子都不会签名。

  • 如果jar在没有写权限的目录中,这将不起作用。我可以向用户发出警告。

  • 如果在程序运行时jar在档案资源管理器(如7zip)中打开,这将无效。我可以通过向用户显示要求他们关闭它的消息来解决这个问题。

  • 这使得部署新版本的jar很烦人,因为保存在jar中。用户将不得不以某种方式从旧jar加载数据,或者开始清理。我也没关系。

  • This won't work with signed jars. That's not a problem for me, since these jars won't be signed anyway.
  • This won't work if the jar is in a directory without write privileges. I can warn the user about that.
  • This won't work if the jar is open in an archive explorer (like 7zip) while the program is run. I can get around this by simply showing the user a message asking them to close it.
  • This makes deploying a new version of the jar annoying, as the saves are inside the jar. The user will either have to somehow load the data from the old jar, or start clean. I'm okay with that too.

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

So, my question is: does any of the above seem particularly horrible? And if so, why?

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

I should note that I'm not really asking about whether the approach is standard or confusing, I'm asking about the code. Is there any reason the code won't work?

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

The "rewriting the contents of the jar while the jar is being run" step is the one that makes me the most nervous, but I can't find any documentation that says you can't, or even shouldn't, do this. If I did this in the middle of the program, then maybe bad things would happen, but this will only happen at the very end of the program. Overwriting the jar is the very last line of the program.

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

And most importantly of all, this all seems to work! I've tested this on Windows 7, Windows 8, and Mac. So if anybody knows of any documentation, or can think of a corner case where this code won't work, I'd definitely appreciate it.

这是一个MCVE展示上述所有的。为此,您必须将此类编译为runnable jar文件以及包含单个数字的saves.txt文件。

Here is an MCVE showing all of the above. For this to work, you have to compile this class into a runnable jar file along with a saves.txt file that contains a single digit.

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"));
    }
}


推荐答案

概念

这有点糟糕。


  • 与使用jar外部的配置文件相比,这是浪费。

  • 它不会真正添加值。用户通常不关心配置文件的存在位置。

  • 它将状态保存在程序中。为什么这么糟糕?因为它们没有分离,所以用户无法在不复制整个程序的情况下轻松转移配置,或者在不共享配置的情况下共享程序。

  • 如果您可以读取并重写jar,则可以还将配置文件写入同一目录。通常。

  • 假设它确实可以正常工作,它会使您的代码复杂化,可能会造成不必要的。

  • 如果它不起作用,您的用户可能会被遗忘破碎的可执行文件。用户体验不佳。

  • It is wasteful, compared to using configuration files outside of the jar.
  • It doesn't really add value. Users usually don't care where the configuration files live.
  • It saves state in the program. Why is this bad? Because they're not seperated, users cannot easily transfer the configuration without copying the whole program, or share the program without sharing their configuration.
  • If you can read and rewrite the jar, you can also write configuration files to the same directory. Usually.
  • Assuming it does work flawlessly, it complicates your code, possibly unnecessarily.
  • If it doesn't work, your users might be left with a broken executable. Not a good user experience.

您无需将其放入同一个jar中。能够将与配置捆绑在一起的jar快照导出到程序中,可能是一个功能,但默认情况下它可能没有足够的价值。

You don't need to put it into the same jar. Being able to export a snapshot of the jar, bundled with the configuration into the program, might be a feature, but it likely isn't valuable enough to have it by default.

实施

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

What happens when the host OS runs out of resources? File descriptors, space... You might get a NullPointerException in java.io.File.<init>, but what state did you leave on the hard-drive? Can your program handle existing files? All this is unneeded complexity, that you could avoid by using files outside of the jar.

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

The writing could fail at any moment, due to external causes. And since you don't use finally blocks to close your streams, I'm not sure if the changes will be flushed.

结论

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

Facing the persistence problem you're having with Processing, this is a clever hack, but still a hack. It will blow up, sometime, for someone. If you can avoid, do it. On the other hand, I might misunderstand the problem with Processing, so there might be solution that is more appropriate.

这篇关于写一个正在运行的罐子的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆