`
pleasetojava
  • 浏览: 739179 次
  • 性别: Icon_minigender_2
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论
阅读更多
1 问题的提出
作为一个经常使用Java编程的程序员,当我在发布我的Java程序的时候,我习惯于这样组织所有的程序和资源:主程序放到JVM系统变量“user.dir”所指向的目录中(假设是MyAppDir目录),程序所用到的工具类(通常是打好包的jar文件)放到MyAppDir/lib/目录,其他资源(如图片等)放到MyAppDir/res/目录,然后写一个批处理文件,在里面设置好环境变量和各种路径,最后将所有的这些东西交给用户。但是如果这个程序不是很大的话,是不是心里觉得这么多目录和文件有点不够简洁?会不会觉得运行bat文件出来的那个Dos窗口有点碍眼?会不会羡慕Windows编程人员可以把所有资源和程序都放到一个exe文件中?下面我们就来着手解决这些问题。
2 解决方案
我们先提出一个实际的例子来说明这个问题:我的应用程序的主类为cn.edu.hlju.ica.app.Main,它位于mainApp.jar。Main类需要用到位于tools.jar中的com.util.Tool类。通常的做法是将tools.jar放到MyAppDir/lib/目录下,然后执行
java –classpath .;./lib/tools.jar –jar mainApp.jar
但是如果我想把tools.jar和mainApp.jar打包到一起,把它们都放到OneJarApp.jar中,然后只需要执行
java –jar OneJarApp.jar
这样该多好啊,尤其是当你需要的jar文件比较多的时候,无论是这些jar包的组织,还是bat文件的书写都是挺麻烦的事。下面我们就分析一下解决方法。
n 第一种:也是最容易想到的方法,就上例来说,我们可以将tools.jar和mainApp.jar都展开,然后将com.util.Tool(本来位于tools.jar中)和cn.edu.hlju.ica.app.Main(本来位于mainApp.jar中)一起打包成OneJarApp.jar。有很多开发环境如JBuilder或干脆用jar命令都可以很容易做到,但是这样做会产生一些很严重的问题:如果Main类不只需要一个而是需要两个分别位于toolsOne.jar和toolsTwo.jar中的类com/a/A.class和com/b/B.class,而A和B都需要引用在各自的jar文件中com/目录下的一个资源,比如说是com/log4J.property,这时如果将它们展开,并入到一个jar文件的时候,你该选择哪一个资源呢?再有,如果toolsTwo.jar的许可明确要求你再重新发布之前不能修改它,你该怎么办?这种方法显然不够通用,而且在某些情况下回出现问题。
n 第二种:在回过头来看我们的实例,既然不能把现有的tools.jar文件展开,那我们干脆就把它和mainApp.jar一起打包成OneJarApp.jar中行吗?即下面的组织形式:
OneJarApp.jar
|--META-INF/MANIFEST.MF
|--mainApp.jar
|--cn/edu/hlju/ica/app/Main.class
|--tools.jar
|--com.util.Tool
然后我们在MANIFEST.MF中制定Class-Path属性,希望类装载器在启动程序的同时也可以正确
的装载其他jar文件。很不幸,这样做是行不通的。因为系统类装载器并不能从嵌入的jar
(tools.jar就是嵌入到了OneJarApp.jar中)文件中读取所需的类。所以我们需要使用自定义类
装载器来完成这项工作,不过不用担心,开源工程One-JAR已经为我们做好了相关工作,我们只需
很少几步操作就可以达成目的。
3 One-Jar简介
One-Jar是SourceForge上的一个开源工程,它使用自定义的类装载器从嵌入的jar文件中载入类和资源,从而可以使原来的程序中所有用到的jar文件(包括主类所在的jar文件)都打包到一个jar文件中,且原来的程序不需要做任何修改,然后我们就可以简单的通过java –jar OneJarApp.jar来运行该应用程序,是不是很理想?
该工程短小精悍,只有三个类,分别是:
com.simontuffs.onejar.JarClassLoader
com.simontuffs.onejar.Boot
com.simontuffs.onejar.Handler
它的实现原理是:程序运行之初首先由One-Jar接手,它先通过其自定义的类装载器装载嵌入的各个jar文件,然后利用reflectionAPI调用你的应用程序的主函数,使你的应用程序得以执行。但是它对各个嵌入的jar文件在宿主jar文件中的位置有一定的要求,即含有主类的jar文件必须放在宿主jar文件的main/目录下,其他的jar文件放到宿主jar文件的lib/目录下。还是上面的例子,主类cn.edu.hlju.ica.app.Main位于mainApp.jar中,Main所用到的com.util.Tool位于tools.jar中,最终要将它们都打包成OneJarApp.jar,其目录结构应如下
OneJarApp.jar
|--META-INF/MANIFEST.MF
|--main
|--mainApp.jar
|--META-INF/MANIFEST.MF
|--cn/edu/hlju/ica/app/Main.class
|--bin
|--tools.jar
|--com.util.Tool
|--com/simontuffs/onejar
|-- Boot.class
|-- Handler.class
|-- JarClassLoader.class
注意,这里的(红色)MANIFEST.MFOne-JAR工程自带的
,而不是你的应用程序的(绿色)MANIFEST.MF(你应用程序的MANIFEST.MFmainApp.jar中
);而One—Jar工程的三个类也必须包含在OneJarApp.jar中。
下面是各个类的代码
Main.java
package cn.edu.hlju.ica.app;
import com.util.*;
public class Main {
public static void main(String[] args) {
new Tool();
}
}
Tool.java
package com.util;
public class Tool {
static {
System.out.println("创建了一个Win32版的Tool实例");
}
}

目录结构如下
最外层的MANIFEST.MF文件内容如下:
Manifest-Version: 1.0
Main-Class: com.simontuffs.onejar.Boot
One-Jar-Expand: expand,doc
执行结果
4 扩展应用
仅仅做到以上功能是不够的,我们在解决实际问题时还会遇到一些更为复杂的情况。比如说如果我们要做串口通信的程序,需要用到SUN的串口包comm.jar,但是Windows平台和Linux平台的串口包虽然API名字相同但是内部实现却不同。所以如果我的程序需要跨平台使用的话,就需要在执行期动态选择相应的串口包。但是按照上面的方法,我们的两个串口包都要放宿主jar文件的lib/目录下,这时会发生什么情况呢?会不会发生混淆呢?答案是会,并且One-Jar也作了一些比较初级的处理操作。还是拿我们上面那个例子。我的程序要同时支持Windows平台和Linux平台,所以我的com.util.Tool有两个版本,分别打包到tools_windows.jar和tools_linux.jar中,然后我将它们都放到OneJarApp.jar的lib/目录下,执行-Done-jar.verbose来查看执行的详细信息:
我们看到,2的位置One-Jar的ClassLoader载入了Linux版的Tool;3的位置上,类装载器又试图载入Tool类的windows版,但是从下面画线部分我们可以看到,Tool的windows版并没有载入成功,因为类装载器不能两次载入同一个类,虽然它们实现不同,但是它们的名字一样,都是com.util.Tool,所以windows版就被之前载入的Linux版屏蔽了。从4看到程序可以正常运行。但是One-Jar并没有给我们控制到底选哪一个的权力,所以我们需要对One-Jar修改和扩充。
我们的目标是One-Jar的自定义的类装载器,我们只需要控制其类的装载过程,即JarClassLoader的load(String,String)方法,就可以达到我们的目的。我们首先看一下load()方法的主要部分:
public String load(String mainClass, String jarName) {
………省略
if (jarName == null) {
jarName = System.getProperty(JAVA_CLASS_PATH);
}
//获得jarName所代表的jar文件中所包含的的全部项目
JarFile jarFile = new JarFile(jarName);
Enumeration myEnum = jarFile.entries();
Manifest manifest = jarFile.getManifest();
while (myEnum.hasMoreElements()) {
JarEntry entry = (JarEntry)myEnum.nextElement();
if (entry.isDirectory())
continue;
………省略
//打印提示信息
INFO("caching " + jar);
VERBOSE("using jarFile.getInputStream(" + entry + ")");
{
// Note: loadByteCode consumes the input stream, so make sure //its scope
// does not extend beyond here.
//注意以下部分
InputStream is = jarFile.getInputStream(entry);
if (is == null)
throw new IOException("Unable to load resource /" + jar + " using " + this);
loadByteCode(is, jar);
}
………省略
}
请注意看红颜色的部分,One-Jar在这里直接用JarEntry entry生成输入流,再由loadByteCode()载入其字节
码,请注意,这里的entry就是要嵌入的jar文件对象,即tools_windows.jar或tools_linux.jar,如果我们在语句
InputStream is = jarFile.getInputStream(entry);
之前判断一下本地操作系统的类型,再结合jar文件的后缀”_windows”或”_linux”就可以选择性的载入与
本地操作系统相配的jar文件了。为此,我生成了JarClassLoaderEx类,它是JarClassLoader的子类,并且只
覆些了JarClassLoader的load(String,String)方法。我们需要通过JVM属性(我自己定义的属:loader.type)
进行控制,如果loader.type=exLoader,则选择采用JarClassLoaderEx,否则保持不变,仍然采用
JarClassLoader。为达到这一目的,我们需要在com.simontuffs.onejar.Boot类中判loader.type属性的值如果
是ExLoader就生成JarClassLoaderEx实例,否则就生成JarClassLoader实例。重要代码片断如下:
这里我定义了一个osType,用来判定本地操作系统的类型。接下来
我们通过jar文件的后缀,就可以判断它是否是适合本地平台了,如果不适合就跳过此jar文件的装载过程。上
面已经有了Windows版的Tool类,这里再给出Linux版的Tool类的实现:
Tool.java--------Linux
package com.util;
public class Tool {
static {
System.out.println("创建了一个Linux版的Tool实例");
}
}
下面我们来看一下结果,首先我们设置JVM系统属性”os.name”为Windows系统时:
当我们设定”os.name”为Linux系统时:
5 后记
通过研究Boot类的代码我们可以看到,One-Jar有一个默认的设置JVM系统变量的的机制,即将一个one-jar.property的文件放到最终的jar文件的根目录下:
你可以在其中设置一些你自己的属性对,由One-Jar负责帮你将其添加加到JVM中。
6 结束语
至此,我们的One-Jar之旅也告一段落了,相信你看完这篇短文后也能自己动手定制属于你自己的One-Jar来实现更丰富的功能,充分享受开源给我们带来的美妙世界。我是用Eclipse来编写全部代码的,如果有需要源码的话,请和我联系。
分享到:
评论

相关推荐

    one-jar-boot-0.95.jar

    one-jar-boot-0.95.jar

    webscarab-one-20110329-1330(最新版)

    webscarab-one-20110329-1330(最新版) webscarab-one-20110329-1330.jar 1、安装JDK 2、鼠标双击webscarab-one-20110329-1330.jar即可

    ksoap2-android-assembly-2.6.5-jar-with-dependencies.jar

    ksoap2-android-assembly-2.6.5-jar-with-dependencies.jar 要是需要最新的,下载地址: http://code.google.com/p/ksoap2-android/

    one-jar-boot:One-JAR:trade_mark:的错误修复

    one-jar-boot是底层的JarClassLoader和其他One-JAR引导机制,其中包含One-JAR引导类的源代码()。 那么这个仓库是做什么用的呢? 不幸的是,某些库(例如“适用于Java的AWS开发工具包”)存在问题。 当由one-jar ...

    neo4j-jdbc-2.3.2-jar-with-dependencies.jar

    java采用jdbc连接neo4j所需要jar

    zkui-2.0-SNAPSHOT-jar-with-dependencies.jar

    zkui-2.0-SNAPSHOT-jar-with-dependencies.jar

    One-JAR(TM)-开源

    One-JAR(TM) 是 Java 中一个棘手问题的简单解决方案:当应用程序依赖于多个其他 jar 文件时,如何将其作为单个 jar 文件分发。 One-JAR使用自定义的类加载器来发现主jar中的库jar文件。

    parquet-tools.jar

    使用方式:java -jar xxx.jar usage: parquet-tools cat [option...] where option is one of: --debug Enable debug output -h,--help Show this help string -j,--json Show records in JSON format. --no-...

    restclient-ui-3.2.2-jar-with-dependencies

    "restclient-ui-3.2.2-jar-with-dependencies" 是这个工具的一个特定版本,该版本包含了所有必要的依赖项,使得用户可以直接运行而无需额外安装其他库。这个版本号表明它是RESTClient的3.2.2迭代,且“jar-with-...

    jstl-1.2.jar免费下载

    jstl-1.2.jar下载jstl-1.2.jar下载jstl-1.2.jar下载jstl-1.2.jar下载jstl-1.2.jar下载jstl-1.2.jar下载jstl-1.2.jar下载jstl-1.2.jar下载jstl-1.2.jar下载jstl-1.2.jar下载jstl-1.2.jar下载jstl-1.2.jar下载jstl-1.2...

    down2mobile.net_ksoap2-android-assembly-2.4-jar-with-dependencies.jar

    down2mobile.net_ksoap2-android-assembly-2.4-jar-with-dependencies.jar 用于方方便调用web service

    ksoap2-android-assembly-3.2.0-jar-with-dependencies

    ksoap2-android-assembly-3.2.0-jar-with-dependencies wcf所用包

    Java-WebSocket-jar

    Java-WebSocket jar包,封装WebSocket实现。

    easypoi-jar包

    easypoi-jar包

    airreceiver-1.2.one-jar.jar

    官方版本,亲测可用

    airreceiver-1.1.one-jar.jar

    官方版本,亲测可用

    mybatis-3.4.1- jar包

    标题中的"mybatis-3.4.1-jar包"指的是MyBatis框架的3.4.1版本的Java Archive(JAR)文件。这个JAR文件包含了MyBatis框架的所有核心类和资源,使得开发者可以在项目中引入并使用MyBatis的功能。 MyBatis的核心功能...

    commons-beanutils-1.8.0 jar包下载

    commons-beanutils-1.8.0 jar包: 1.commons-beanutils-1.8.0.jar 2.commons-beanutils-1.8.0-javadoc.jar 3.commons-beanutils-1.8.0-sources.jar 4.commons-beanutils-bean-collections-1.8.0.jar 5.commons-...

    hadoop最新版本3.1.1全量jar包

    hadoop-annotations-3.1.1.jar hadoop-common-3.1.1.jar hadoop-mapreduce-client-core-3.1.1.jar hadoop-yarn-api-3.1.1.jar hadoop-auth-3.1.1.jar hadoop-hdfs-3.1.1.jar hadoop-mapreduce-client-hs-3.1.1.jar ...

    jarjar-1.4.jar

    jarjar-1.4.jar是该工具的一个版本,虽然现在可能已有更新的版本,但1.4版本仍被许多开发者用于处理特定的兼容性问题。 使用jarjar.jar的步骤通常包括以下几个部分: 1. **规则定义**:首先,我们需要定义规则文件...

Global site tag (gtag.js) - Google Analytics