這是一篇描述怎樣制作自解壓jar文件的文章,作者通過自己編寫的一個自解壓程序,並把這個自解壓程序以及一個manifest文件一起加入原始的jar文件中,就制作出一個可以在各種支持java的平台上運行的自解壓的jar 文件。
自解壓文件
我們先來了解一下自解壓文件,在window下可以用自解壓制作工具如winzip self-Extractor來制作自解壓文件,這些工具會把一個zip文件與解壓程序打包在一起而產生一個新的可執行文件。然後只要運行這個可執行文件,就可以把zip文件中的內容解開。那為什麼要創建自解壓jar文件呢,創建成自解壓zip文件不就好了?我們應該注意到自解壓jar文件可以在任意支持java的平台上解開並執行,例如,可以在linux下執行。創建jar自解壓文件很簡單,只需要一個特殊的JAR manifest文件、一個基於java的解壓程序(這個程序是原作者寫的)、包含基本文件的jar 或者zip文件以及任何jsdk的jar應用程序
manifest文件
要生成可執行jar文件,需要在META-INF 目錄下的manifest文件,文件名為:MANIFEST.MF ,但在我們這裡我們只需要在文件中指定在這個基於java 的解壓程序中包含main()的類的名稱:Main-Class: ZipSelfExtractor
我們已經把一個叫做jarmanifest的文件加入到這個技巧的源程序包中。
解壓程序
你可以用各種方法來實現這個解壓程序,在我們這裡使用了一個簡單直接的辦法。首先,解壓程序判斷這個自解壓jar文件的名稱,有了這個文件名,解壓程序使用解壓標准,把文件解開。具體的可以查看在源碼包中的ZipSelfExtractor.java文件。
值得一提的是這裡用了一個很巧妙的辦法獲取jar文件的文件名,雖然在命令行中出現的這個文件的名字,但它並沒有作為參數傳入類的main()中,因此,這裡使用了以下的代碼來獲取文件名:
private String getJarFileName ()
{
myClassName = this.getClass().getName() + ".class";
URL urlJar =
this.getClass().getClassLoader().getSystemResource(myClassName);
String urlStr = urlJar.toString();
int from = "jar:file:".length();
int to = urlStr.indexOf("!/");
return urlStr.substring(from, to);
}
請注意:getSystemResource() 中使用了myClassName而不是ZipSelfExtractor.class作參數,這使得我們可以更改加壓程序的名字而不需要修改代碼。
接下來,我們來分析獲得這個jar文件的名字。首先,可以獲取指向包含正在運行類的文件,urlStr = urlJar.toString();有了這個url,把jar文件名去掉,剩下的就是我們想要的,下面是這個url的格式:
jar:file:/home/test/zipper.jar!/ZipSelfExtractor.class
有了文件名,就可以開始解壓,詳細的解壓算法請大家自己看源碼。
為了可以更方便實用,程序使用了圖形界面,程序中使用了JFileChooser類可以選擇要解壓的目標目錄。
最後程序還確保不把這兩個文件:manifest文件和extractor's .class(在我們這裡是ZipSelfExtractor.class)文件也解出來,程序是用來解開原始的jar的內容,而這兩個文件並屬於jar原始內容。
打包jar文件
有了manifest文件與解壓程序,我們就可以創建自解壓jar文件了,以下是一個例子:
1.創建一個zip文件Myzip.zip
2.下載zipper.jar
3.把文件解到當前目錄,准備制作自解壓jar文件
java -jar zipper.jar
4.把zipper.class拷貝成 ZipSelfExtractor.class
5.把 myzip.zip 重命名為 myzip.jar
6.把myzip.jar中的內容替換為jarmanifest和ZipSelfExtractor.class這兩個文件
jar uvfm myzip.jar jarmanifest ZipSelfExtractor.class
7.執行java -jar myzip.jar就可以看到效果了,試試看
後記
一個自解壓的jar文件能夠很好的跨平台使用,自解壓jar文件創建簡單,只需要有jre1.2或或者更新的版本就可以實現了。
附自解壓程序的源代碼:
/* ZipSelfExtractor.java */
/* Author: Z.S. Jin
Updates: John D. Mitchell */
import java.io.*;
import java.net.*;
import javax.swing.*;
import java.util.zip.*;
import java.util.*;
import java.text.*;
public class ZipSelfExtractor extends JFrame
{
private String myClassName;
static String MANIFEST = "META-INF/MANIFEST.MF";
public static void main(String[] args)
{
ZipSelfExtractor zse = new ZipSelfExtractor();
String jarFileName = zse.getJarFileName();
zse.extract(jarFileName);
System.exit(0);
}
ZipSelfExtractor()
{
}
private String getJarFileName()
{
myClassName = this.getClass().getName() + ".class";
URL urlJar = this.getClass().getClassLoader().getSystemResource(myClassName);
String urlStr = urlJar.toString();
int from = "jar:file:".length();
int to = urlStr.indexOf("!/");
return urlStr.substring(from, to);
}
public void extract(String zipfile)
{
File currentArchive = new File(zipfile);
JFileChooser fc = new JFileChooser();
fc.setCurrentDirectory(new File("."));
fc.setDialogType(JFileChooser.OPEN_DIALOG);
fc.setDialogTitle("Select destination directory for extracting " +
currentArchive.getName());
fc.setMultiSelectionEnabled(false);
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (fc.showDialog(ZipSelfExtractor.this, "Select")
!= JFileChooser.APPROVE_OPTION)
{
return; //only when user select valid dir, it can return approve_option
}
File outputDir = fc.getSelectedFile();
byte[] buf = new byte[1024];
SimpleDateFormat formatter = new SimpleDateFormat ("MM/dd/yyyy hh:mma",Locale.getDefault());
ProgressMonitor pm = null;
boolean overwrite = false;
ZipFile zf = null;
FileOutputStream out = null;
InputStream in = null;
try
{
zf = new ZipFile(currentArchive);
int size = zf.size();
int extracted = 0;
pm = new ProgressMonitor(getParent(), "Extracting files...", "starting", 0, size-4);
pm.setMillisToDecideToPopup(0);
pm.setMillisToPopup(0);
Enumeration entries = zf.entries();
for (int i=0; i<size; i++)
{
ZipEntry entry = (ZipEntry) entries.nextElement();
if(entry.isDirectory())
continue;
String pathname = entry.getName();
if(myClassName.equals(pathname) || MANIFEST.equals(pathname.toUpperCase()))
continue;
extracted ++;
pm.setProgress(i);
pm.setNote(pathname);
if(pm.isCanceled())
return;
in = zf.getInputStream(entry);
File outFile = new File(outputDir, pathname);
Date archiveTime = new Date(entry.getTime());
if(overwrite==false)
{
if(outFile.exists())
{
Object[] options = {"Yes", "Yes To All", "No"};
Date existTime = new Date(outFile.lastModified());
Long archiveLen = new Long(entry.getSize());
String msg = "File name conflict: "
+ "There is already a file with "
+ "that name on the disk!\n"
+ "\nFile name: " + outFile.getName()
+ "\nExisting file: "
+ formatter.format(existTime) + ", "
+ outFile.length() + "Bytes"
+ "\nFile in archive:"
+ formatter.format(archiveTime) + ", "
+ archiveLen + "Bytes"
+"\n\nWould you like to overwrite the file?";
int result = JOptionPane.showOptionDialog(ZipSelfExtractor.this,
msg, "Warning", JOptionPane.DEFAULT_OPTION,
JOptionPane.WARNING_MESSAGE, null, options,options[0]);
if(result == 2) // No
{
continue;
}
else if( result == 1) //YesToAll
{
overwrite = true;
}
}
}
File parent = new File(outFile.getParent());
if (parent != null && !parent.exists())
{
parent.mkdirs();
}
out = new FileOutputStream(outFile);
while (true)
{
int nRead = in.read(buf, 0, buf.length);
if (nRead <= 0)
break;
out.write(buf, 0, nRead);
}
out.close();
outFile.setLastModified(archiveTime.getTime());
}
pm.close();
zf.close();
getToolkit().beep();
JOptionPane.showMessageDialog
(ZipSelfExtractor.this,
"Extracted " + extracted +
" file" + ((extracted > 1) ? "s": "") +
" from the\n" +
zipfile + "\narchive into the\n" +
outputDir.getPath() +
"\ndirectory.",
"Zip Self Extractor",
JOptionPane.INFORMATION_MESSAGE);
}
catch (Exception e)
{
System.out.println(e);
if(zf!=null) { try { zf.close(); } catch(IOException ioe) {;} }
if(out!=null) { try {out.close();} catch(IOException ioe) {;} }
if(in!=null) { try { in.close(); } catch(IOException ioe) {;} }
}
}
}