不能运行的JAR文件可以使用Java -jar运行
摘要
本文展示如何将不可运行的JAR变为可运行的,并且不用直接操作manifest文件。你将学会开发一个短小的程序使得任何JAR文件都可以使用java -jar命令或者通过在像Windows上双击而运行。
你可以将一个应用的所有类和资源打包到一个JAR文件中。实际上,那就是jar文件的一个目的。另外一个目的是让用户可以非常轻易的执行存储在JAR文件中的应用,那么为什么当他们可以成为一等公民而和本机可执行程序等同的时候,我们为什么要让他们只承担包的功能而成为java世界中的二等公民呢?
要执行一个jar文件,你可以使用java命令的-jar选项。例如你有一个可运行的文件名为myjar.jar的JAR文件,因为它是可运行的,你可以像这样执行它:java -jar myjar.jar
另外,当JRE安装在像Windows这样的操作系统上时,将jar文件和JVM关联后你就可以双击他们运行应用了。这些JAR必须是可运行的。
问题是:你如何让一个JAR是可运行的?
manifest文件和Main-Class条目
在大部分JAR文件中,META-INF目录下会有一个MANIFEST.MF文件,在那个文件中有一个非凡的条目Main-Class,它告诉java -jar命令去执行那么类。
问题是你必须自己恰当的将这个非凡条目加到manifest文件中:它必须位于特定的位置并且必须符合特定的格式,然而有些人不喜欢编辑配置文件。
让API帮你做
从Java 1.2开始引人的java.util.jar包可以让你操作jar文件(注重:它建立在java.util.zip包的基础上)。更确切的说法是,java.util.jar可以让你通过Manifest类非常轻易的操作那个非凡的manifest文件。
让我们编写一个程序使用那个API。首先这个程序必须知道三件事情:
1. 我们希望可以执行的JAR
2. 我们希望执行的主类(这个类必须存在于JAR内)
3. 新的JAR文件的文件名,因为我们不应该简单的覆盖那些文件
编写程序
上面的列表将会构成我们的程序的参数,基于这一点,让我们为这个应用挑选一个合适的名字。MakeJarRunnable听起来如何?
检查main的参数
假设我们的main入口是一个标准的main(String[])方法,我们首先应该检查程序的参数:
if (args.length != 3) { System.out.println("Usage: MakeJarRunnable " + " "); System.exit(0); }
请注重参数列表是如何被解释的,因为这对于后面的代码是非常重要的。参数的顺序和内容并不是硬性设置的,但是假如你改变它们也要记得适当的修改其他的代码。
访问JAR和它的manifest文件
首先我们必须创建一些知道JAR和manifest文件的对象:
//Create the JarInputStream object, and get its manifest JarInputStream jarIn = new JarInputStream(new FileInputStream(args[0])); Manifest manifest = jarIn.getManifest(); if (manifest == null) { //This will happen if no manifest exists manifest = new Manifest(); }
设置Main-Class属性
我们将Main-Class条目放到manifest文件的主要属性部分。一旦我们从manifest对象获得了这个属性集我们就可以设置适当的主类。然而假如一个Main-Class属性已经存在于原来的JAR时怎么办?这个程序简单的打印一个警告并退出。或许我们可以增加一个命令行参数告诉程序用新的值替换已经存在的那个值.
Attributes a = manifest.getMainAttributes(); String oldMainClass = a.putValue("Main-Class", args[1]); //If an old value exists, tell the user and exit if (oldMainClass != null) { System.out.println("Warning: old Main-Class value is: " + oldMainClass); System.exit(1); }
输出新的JAR
我们需要创建一个新的jar文件,因为我们必须使用JarOutputStream类。注重我们必须保证没有将输入作为输出使用。作为替代,也许程序应该考虑两个jar文件相同并且提示用户是否覆盖原来的。然而我将这个保留给读者作为练习。
System.out.println("Writing to " + args[2] + "..."); JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(args[2]), manifest);
我们必须将原来的JAR中的每个条目都写到新的JAR中,因为对那些条目迭代:
//Create a read buffer to transfer data from the input byte[] buf = new byte[4096]; //Iterate the entries JarEntry entry; while ((entry = jarIn.getNextJarEntry()) != null) { //Exclude the manifest file from the old JAR if ("META-INF/MANIFEST.MF".equals(entry.getName())) continue; //Write the entry to the output JAR jarOut.putNextEntry(entry); int read; while ((read = jarIn.read(buf)) != -1) { jarOut.write(buf, 0, read); } jarOut.closeEntry(); } //Flush and close all the streams jarOut.flush(); jarOut.close(); jarIn.close();
完整程序
当然我们必须将这些代码放到一个类里面的main方法里面,并且具有合适的import声明。
使用范例
让我们用一个范例来使用这个程序。假设你有一个应用其main入口点是类HelloRunnableWorld(这个是它的全类名,也就是包含包名),同样假设你已经创建了一个名字为myjar.jar的JAR,包含整个应用。对于这个jar,我们像这样运行MakeJarRunnable:
java MakeJarRunnable myjar.jar HelloRunnableWorld myjar_r.jar
再强调一次,就像早先提到的,注重参数列表的顺序。假如忘记了顺序,以无参的形式运行程序它就会告诉你使用信息。
使用java -jar命令运行myjar.jar和myjar_r.jar,注重它们的差异。完成这些之后,查看一下它们的manifest文件(META-INF/MANIFEST.MF)。
这里有一个建议:将MakeJarRunnable制作成一个可以运行的JAR!
运行它
通过双击一个JAR或者使用简单的命令总是比将它包含在你的classpath并运行特定的main类方便。为了帮助你作到这一点,JAR规范为JAR的manifest文件提供了一个Main-Class属性。我在这里提出的这个程序让你利用Java的JAR API非常轻易的操作这个属性并制作你自己的可运行的JAR。