怎样制作自解压的jar文件

Create easy-to-use, cross-platform Java Archives

Summary
This tip presents an easy way to make self-extracting Java Archives (JARs). A self-extracting archive can extract the contained files to user-selected directories on any Java platform. Your users are only required to have a Java runtime system. (1,200 words; November 16, 2001)



For many years, Phil Katz's archive creation, zip, has been one of the most popular file archiving formats. Sun has adopted the zip format as the basis for the Java Archive (JAR). Sun has extended the zip format's use with various conventions so that you can pack Java classes into an archive file. With the addition of the JAR manifest file, the Java runtime can easily locate and directly execute the main class of the Java application contained in the jar file.

Some zip utility tools can create self-extracting archives for various platforms, such as MS Windows. The utility tool combines a regular zip archive with an extractor program to generate a new executable (exe) file. Receivers of the exe file only need to run it to extract the original zip archive's contents. The executable runs the extractor program to extract the archived files into a user-specified output directory.

You can convert a base zip or jar file into an executable jar file on any Java platform. Whereas the self-extracting zip can only create platform-specific executables, the self-extracting jar file can be distributed to and run on any platform supporting Java.

Creating the self-extracting jar file is straightforward. You just need a special JAR manifest file, a Java-based extraction program, the zip or jar file containing the base content files, and any Java SDK's jar utility application.

The manifest file
To make executable JARs, you first need a manifest file called MANIFEST.MF in the META-INF directory. The manifest file may contain a number of possible entries; however, for our purposes here, we just need to specify the name of the Java class that contains the Java-based extractor program's main() method:

Main-Class: ZipSelfExtractor

We've added a manifest file named jarmanifest to this tip's example code. For more information about the manifest file, see the Jar File Specification.

The extractor
You can make the extractor program using various approaches. The approach we present here is simple and straightforward. First, the extraction program figures out the self-extracting jar file's name. With that name in hand, the extractor utilizes the standard, built-in Java zip/jar libraries to extract the content files from the archive. You can find the full source code for ZipSelfExtractor in ZipSelfExtractor.java.

Getting the jar filename in the extractor program can be tricky. Although the jar file's name appears on the command line, that name is not passed to the class's main() method. Therefore, in the extractor program, we use the following code to extract the information from the URL that points to the extractor:


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

Notice that in the getSystemResource() method we pass myClassName instead of ZipSelfExtractor.class. That lets us change the extractor program name without changing that part of the code. We set myClassName by looking up the current class's name.

Next, we extract the jar file's name. First, we ask for a URL to the class file containing the currently running class (which is the extractor program). Once we have the URL, we can snip out the jar file's name. By definition, the URL of the JAR extractor program follows the basic format:

  1. jar:, which shows that the executable runs from inside a jar file
  2. The jar file's URL, such as file:/C:/temp/test.jar, followed by the ! character
  3. The internal path name of the file within the JAR, such as /ZipSelfExtractor.class

In the extractor program's case, the URL might look like:

jar:file:/home/johnm/test/zipper.jar!/ZipSelfExtractor.class

Now that we have the jar file's name, we can perform the extraction. The guts of the extraction program rely on the built-in, Java zip/jar file manipulation libraries to uncompress the content files contained in the archive. See Resources for more on the zip/jar file manipulation libraries.

For ease of use, the extractor is a graphical Java application. The application uses the JFileChooser class to let users specify the destination directory to which they want the files extracted. A ProgressMonitor shows the progress of the extraction process. If a file might overwrite an already existing file, the user is asked whether or not to overwrite the existing file. At the conclusion, a standard dialog box presents extraction statistics.

Finally, the extractor program checks that it does not extract the files that make the jar file self-extracting -- the manifest file and the extractor's .class file; the program should just extract the original JAR contents. Those two files are artifacts of the self-extracting jar file and not part of the original, base content files.

Packing the jar file
Now that we have the manifest file and the extractor program, we can build the self-extracting jar file. We can manually use the JDK's jar utility to make a self-extracting jar file. For example, assuming you have a zip file called myzip.zip, you can perform the following steps to make a self-extracting file from it:

  1. cd to the directory containing myzip.zip
  2. Download zipper.jar
  3. Extract the files into the current directory. We've made it a self-extracting JAR:

    java -jar zipper.jar

  4. Copy the zipper.class file to ZipSelfExtractor.class
  5. Rename myzip.zip as myzip.jar
  6. Update myzip.jar with the jarmanifest and ZipSelfExtractor.class files:

    jar uvfm myzip.jar jarmanifest ZipSelfExtractor.class

Now myzip.jar is self-extracting on all platforms containing Java Runtime Environment (JRE) 1.2 or later. To execute the self-extracting jar file, run:

java -jar myzip.jar

Note that some platforms may have bindings already set up such that you can execute the jar file just by clicking on the myzip.jar file icon, which will run the command line equivalent.

Exercise for the reader
The current ZipSelfExtract does not integrate well if you make a self-extracting JAR out of an existing jar file containing a manifest file. Add intelligence to the self-extractor and the creation instructions so you can deal with existing jar files that contain manifest files.

Free your hand from the JAR
A self-extracting jar file is a good mechanism for cross-platform file distribution. Self-extracting JARs are easy to create, and the minimal user requirement of a JRE 1.2 or later installation is a reasonable tradeoff for gaining cross-platform support.

Instead of manually creating the self-extracting jar file, check out ZipAnywhere. ZipAnywhere is a full-featured zip/jar utility tool written in 100% pure Java. It is a free GUI-based tool a la WinZip and can create self-extracting jar files with the click of a button.

Reference Source Code:

/* 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) {;} }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值