我正在尝试创建一个Jar文件,其中包含Jar 中的数据,这些数据在程序的执行之间持续存在。我知道还有其他方法可以保存数据,但我想要一个完全独立的Jar文件。
我有一些似乎有用的东西,我希望对你在我的方法中看到的任何漏洞提出反馈,因为感觉对它有所了解。我正在做的是:
我正在做所有这些麻烦来解决这个错误,你不能只更新jar中的单个文件:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4129445
而且我没有使用jar -u命令,因为我不想要求最终用户拥有JDK。
我可以看到这方面的一些问题,但它们都不是我的交易破坏者:
所以,我的问题是:以上任何一项看起来特别可怕吗?如果是这样,为什么?
编辑:我应该注意,我并不是在问这个方法是标准的还是令人困惑的,我问的是代码。是否有任何理由代码不起作用?
“罐子正在运行时重写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"));
}
}
答案 0 :(得分:3)
<强>概念强>:
有点可怕。
你不需要把它放在同一个罐子里。能够将与配置捆绑在一起的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);
}