提升工作效率利器:
Mac App Store 上的“Whale - 任务管理、时间、卡片、高效率”
jar可执行包打包规范
首先我们介绍打包规范:将我们自己的工程打成jar的可执行包的主要规范在于清单文件的书写“META-INF/MANIFEST.MF”,对于这个文件,你多写一个空格多打一个回车换行都是要不得的。
那么“MANIFEST.MF”清单文件到底是做什么用的呢?想想看,我们想运行一个我们自己打的 *.jar包时,系统是如何找到程序的入口函数main()的呢?程序是如何找到我们所引用的外部jar包里的类的呢?没错就是通过这个文件来配置的,当我们双击一个可执行的*.jar包时,就会去读取这个文件,从而找到入口类,读取该文件的配置的class path就可以找到我们工程里所引用的其他jar包里的类,虚拟机就会加载我们程序所有会用到的类,如果虚拟机在配置的路径下找不到会用到的类,就会抛出class Not found Exception。
对于“MANIFEST.MF”文件,我们只讲常用的配置,如下:
Manifest-Version: 1.0
Main-Class: log/SangoLogApplication
Class-Path: lib/commons-configuration-1.5.jar
lib/commons-collections-3.2.jar
lib/commons-lang-2.3.jar
lib/commons-logging-1.1.1.jar
Manifest-Version: 1.0 配置的是版本号,单独写一行,冒号后要留一个空格,本行末尾不能有空格。
Main-Class: log/SangoLogApplication 配置的是入口类的路径,SangoLogApplication就是工程的入口类的类名,冒号后要留一个空格,本行末尾不能有空格。
Class-Path: lib/commons-configuration-1.5.jar 配置的是本工程所引用的外部包所在的路径,冒号后要留一个空格,最后一个外部包的末尾不能有空格,结束时要回车换行。由于外部包可以有多个,Class-Path配置多个外部包的规范写法就有两种。其一,所有的外部包都写在Class-Path后的同一行,每个外部包之间通过一个空格隔开。其二,写在多行,同一行的不同外部包之间以空格隔开,不同行的外部包在行首至少要留有两个空格。不管是两种写法的哪一种,最有一个外部包的后面不能留有空格,同时需要在最后一个外部包的后面回车换行。
jar的打包方法
1、通过命令行打包。
2、通过eclipse自带的打包功能打包。
3、通过在eclipse上安装fat.jar插件打包。
打包方法参见:Eclipse将引用了第三方jar包的Java项目打包成jar文件的两种方法 - Alexia(minmin) - 博客园
打完包了,有没有想过 *.jar 包到底是一个什么东东呢?
首先可以肯定的是 *.jar 包是一个类似于 *.zip *.rar的java压缩包。我们都知道压缩包是一个文件而不是一个文件夹,从而推断 *.jar也是一个文件,这一推断是没问题的, *.jar确实是一个文件而不是一个文件夹。
*.jar是一个文件会带来什么问题呢?
问题一:
如文章标题一样,相信很多人都遇到过资源文件找不到,但是自己工程自己写好编译好的class文件却能找的到的问题吧,等下一一解说,这种问题实在不能让他再出现,对其排查太浪费时间,如果你误以为是自己打包不规范排查的代价会更大,因为确实有可能是因为规范引起的,后面我们会一并介绍清楚jar的打包规范。
问题二:
工程引用了JDK以外的外部jar包,打完jar包双击执行会报Could not found the main class,如下图所示,这个错其实不是找不到main类,好笑吧,如果你误以为是找不到main类,那你的时间就会被大量的要去,那这种问题到底是什么问题呢?用dos 命令行执行java -jar *.jar 执行我们的jar包(-jar的目的是忽略目前的classpath,从manifest.mf文件中读取classpath),就会发现,其实是在虚拟机加载外部包的类时抛出了Class not found Exception。发现了问题所在,离解决问题也就不远了。
其实上面两个问题的实质是同一个,当我们把所有的外部包或者外部资源都放在我们自己的 *.jar里时,我们还误认为这些外部包和外部资源和我们的执行的程序都在同一个根目录下,此时java虚拟机加载需要用到的外部包时就抛出类找不到异常,代码里试图打开一个外部资源时就会抛出文件找不到异常。
jar包中相对路径的处理
上面提到的两个问题主要是因为jar包是一个单独的文件而非文件夹,绝对不可能通过"file:/e:/.../ResourceJar.jar/resource /res.txt"这种形式的文件URL来定位res.txt。所以即使是相对路径,也无法定位到jar文件内的txt文件(读者也许对这段原因解释有些费解,在下面我们会用一段代码运行的结果来进一步阐述)。对于外部包也是同样的道理。
那么把资源打入jar包,无论ResourceJar.jar在系统的什么路径下,jar包中的字节码程序都可以找到该包中的资源。这种想法能实现吗?
我们可以用类装载器(ClassLoader)来做到这一点:
(1) ClassLoader 是类加载器的抽象类。它可以在运行时动态的获取加载类的运行信息。 可以这样说,当我们调用ResourceJar.jar中的Resource类时,JVM加载进Resource类,并记录下Resource运行时信息(包括Resource所在jar包的路径信息)。而ClassLoader类中的方法可以帮助我们动态的获取这些信息:
● public URL getResource(String name)
查找具有给定名称的资源。资源是可以通过类代码以与代码基无关的方式访问的一些数据(图像、声音、文本等)。并返回资源的URL对象。
● public InputStream getResourceAsStream(String name);
返回读取指定资源的输入流。这个方法很重要,可以直接获得jar包中文件的内容。
(2) ClassLoader是abstract的,不可能实例化对象,更加不可能通过ClassLoader调用上面两个方法。所以我们真正写代码的时候,是通过Class类中的getResource()和getResourceAsStream()方法,这两个方法会委托ClassLoader中的getResource()和getResourceAsStream()方法 。
其用法的例子:URL fileURL=this.getClass().getResource("/resource/res.txt");
这种方法需要在eclipse下配置一下source,project -> Java Build Path -> source -> add Folder 把资源路径包括进来。