2014年5月3日 星期六

如何製作可執行的 Java 壓縮檔 jar

雖然會用 Eclipse, 但工具很龐大, 啟動要一點時間, 所以我一直都習慣用 DOS 畫面執行 Java. 但編譯後可能會產生多個 class 檔 (例如內部類別), 攜帶不方便, 而且要使用者打開 DOS 視窗, 敲入一長串執行指令, 在 GUI 為王道的今天, 實在太委屈了, 應該將整個程式壓縮成單一的可執行 jar 檔較好. 下面將以之前測試 JTextField 時的一個範例檔案為例來說明.

# Java Swing 測試 : JLabel 與文字欄位

這篇文章被 Google 翻譯弄亂了, 因為小狐貍們也跟我一樣用 Chrome, 結果菁菁有一次啟動了 "Google 翻譯" 我不知道, 存檔後才發現它把程式碼也翻譯了, 目前暫時還沒時間去一一修復, 擱著吧. 我們要用的是其中幾乎無損的範例 9 (JTextField6.java), 但此處要做個修改, 就是為了製作 jar 檔時抓相關的 class 方便, 希望將編譯出來的 class 檔都集中放在指定目錄中, 這就要用到 package 觀念了, 我們在原來的 JTextField6.java 前面加上 package mypackage :

package mypackage;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

public class JTextField6 implements ActionListener {
  JFrame f;
  JTextField text1,text2;
  JButton cut, copy, paste;
  public static void main(String argv[]) {
    new JTextField6(); 
    }
  public JTextField6() {
    JFrame.setDefaultLookAndFeelDecorated(true);
    JDialog.setDefaultLookAndFeelDecorated(true);
    f=new JFrame("JTextField6");
    f.setSize(400,300);
    f.setLocationRelativeTo(null);
    Container cp=f.getContentPane();
    cp.setLayout(null);
    text1=new JTextField();
    text1.setBounds(20,20,200,25);
    cp.add(text1);
    cut=new JButton("剪下");
    cut.setBounds(20,70,60,25);
    cut.addActionListener(this);
    cp.add(cut);
    copy=new JButton("複製");
    copy.setBounds(90,70,60,25);
    copy.addActionListener(this);
    cp.add(copy);
    paste=new JButton("貼上");
    paste.setBounds(160,70,60,25);
    paste.addActionListener(this);
    cp.add(paste);
    text2=new JTextField();
    text2.setBounds(20,120,200,25);
    cp.add(text2);
    f.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);    
    f.setVisible(true);
    f.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        int result=JOptionPane.showConfirmDialog(f,
                   "確定要結束程式嗎?",
                   "確認訊息",
                   JOptionPane.YES_NO_OPTION,
                   JOptionPane.WARNING_MESSAGE);
        if (result==JOptionPane.YES_OPTION) {System.exit(0);}
        }   
      });
    }
  public void actionPerformed(ActionEvent e) {
    if (e.getSource()==cut) {text1.cut();}
    else if (e.getSource()==copy) {text1.copy();}
    else if (e.getSource()==paste) {text2.paste();}
    }
  }

編譯時要加點料, 不是 javac JTextField6.java, 這樣仍然是在目前目錄下產生 class 檔, 亦即有沒有加 package 都一樣. 要讓 class 檔放在 package 目錄下, 必須這樣編譯 :

javac -d . JTextField6.java

注意, 多了一個選項 -d 與小數點, 表示要在目前目錄下建立一個 package 所指定之子目錄, 並將產生的 class 檔放在裡面. 例如 JTextField6.java 是在 G:\java\JT 下, 則編譯後, 會建立一個 mypackage 子目錄, 並將產生的兩個 class 檔 : JTextField6.class 與 JTextField6$1.class 放在此子目錄下 :


 一個專案通常由多個 java 檔組成, 可以將這些 java 檔都指定為相同 package 名稱, 然後只用下面指令一次編譯全部該專案的 java 檔, 產生的 class 檔就全部放在 package 所指定的子目錄下 :

javac -d . *.java

然後在工作目錄, 即 java 檔所在目錄, 下達壓縮指令 :

jar cvf JTextField6.jar mypackage

第二個參數是壓縮後的檔案, 可自訂; 但最後一個參數則必須是要壓縮的 package 目錄名. 這樣就會在工作目錄下產生 JTextField6.jar 檔案 :

L:\Java\JT>jar cvf JTextField6.jar mypackage
已新增資訊清單
新增: mypackage/ (讀=0)(寫=0)(儲存 0%)
新增: mypackage/JTextField6$1.class (讀=801)(寫=557)(壓縮 30%)
新增: mypackage/JTextField6.class (讀=2132)(寫=1191)(壓縮 44%)
使用 RAR 或 Winzip 解壓縮此 jar 檔就可以得到 class 檔案群. 事實上 jar 檔就是 zip 檔, 直接把副檔名 .jar 改成 .zip 解壓縮如下 :


可見 jar 指令除了將 package 目錄壓進去外, 還加了一個 META-INF 目錄, 裡面有一個 MENIFEST.MF 檔案, 內容如下 :

Manifest-Version: 1.0
Created-By: 1.7.0_51 (Oracle Corporation)

執行 jar 檔指令如下 :

java -cp JTextField6.jar mypackage/JTextField6

必須指定 package 名稱以及主 class 名稱才行. 但是在檔案總管直接點擊 jar 檔是無法執行的, 那要怎麼做呢? 其實只要在壓縮 jar 檔時, 在上面那個 MANIFEST.MF 檔案裡添加 Main-Class 屬性, 指定主類別名稱就可以了.

首先我們要準備一個文字檔, 例如 manifest.txt, 其內容如下 :

Main-Class: mypackage.JTextField6

注意, 打完須 enter, 否則無效. 這裡主類別是在 mypackage 套件下, 所以務必註明套件名稱, 如果只寫 JTextField6 將找不到主類別. 然後重新壓縮 :

L:\Java\JT>jar cvmf manifest.txt JTextField6.jar mypackage
已新增資訊清單
新增: mypackage/ (讀=0)(寫=0)(儲存 0%)
新增: mypackage/JTextField6$1.class (讀=801)(寫=557)(壓縮 30%)
新增: mypackage/JTextField6.class (讀=2132)(寫=1191)(壓縮 44%)

這樣就可以直接用 -jar 選項執行 jar 檔了.

L:\Java\JT>java -jar JTextField6.jar

而且直接在檔案總管點擊 jar 檔也可以執行了.

把 jar 副檔名改為 zip 去看壓縮檔中 META-INF 目錄下的 MANIFEST.MF 內容 :

Manifest-Version: 1.0
Created-By: 1.7.0_51 (Oracle Corporation)
Main-Class: mypackage.JTextField6

可見在壓縮時, jar 工具程式會讀取我們指定的 manifest.txt 檔內容, 然後寫入 MANIFEX.MF 檔裡. 當然, 如果不要設 package 也是可以的, 把專案中全部要用到 class 都放在同一目錄下, 然後於 manifest.txt 檔案中指定主類別, 然後在 jar 指令中列舉各 class 即可 :

jar cvmf manifest.txt ABC.jar A.class B.class C.class

參考資料 :

# Java Gossip: 製作 Executable JAR
# 可執行的 jar 檔

沒有留言 :