写点什么

☕️【Java 技术之旅】知识盲点关于 jar 包的点点滴滴

发布于: 2021 年 06 月 03 日
☕️【Java 技术之旅】知识盲点关于jar包的点点滴滴

每日一句

不同的人生,有不同的幸福。去发现你所拥有幸运,少抱怨上苍的不公,把握属于自己的幸福。你,我,我们大家都可以经历幸福的人生。

背景介绍

经常会头疼于一个 jar 包是如何制作的,包括 maven 的打包方式,springboot 的打 jar 包的原理,jar 包稍稍有错误就会完全无法运行。

压缩 jar 包

大家都知道,java 应用项目按照 JSR 标准协议,一般采用 jar 包进行运行,java 程序可以打包成一个 jar。此外,当然你必须指定一个拥有 main 方法的 main class 类作为 Java 程序的入口,同时它也是启动 JVM 进程的入口

制作只含有字节码文件的 jar 包

我们先来看只含有字节码文件,即只含有 class 文件的 jar 包怎么制作,这是最简单的形式

最简单的 jar 包——直接输出 hello

最终生成的 jar 包结构


META-INFHello.class
复制代码


方法步骤


  1. 用记事本写一个 Hello.java 的文件


class Hello{     public static void main(String[] agrs){         System.out.println("hello");     }}
复制代码


  1. 用命令行进入到该目录下,编译这个文件


javac Hello.java 
复制代码


  1. 将编译后的 Hello.class 文件打成 jar 包


jar -cvf hello.jar Hello.class 
复制代码


  • c(create):表示要创建一个新的 jar 包,

  • v(view):表示创建的过程中在控制台输出创建过程的一些信息。

  • f(file)表示给生成的 jar 包命名


  1. 运行 jar 包


java -jar hello.jar
复制代码


这时会报如下错误 hello.jar 中没有主清单属性。


  1. 添加 Main-Class 属性


  • 用压缩软件打开 hello.jar,会发现里面多了一个 META-INF 文件夹,里面有一个 MANIFEST.MF 的文件,用记事本打开。

  • 在第三行的位置写入 Main-Class: Hello (注意冒号后面有一个空格,整个文件最后有一行空行),保存

  • 再次运行 java -jar hello.jar ,此时成功在控制台看到 hello ,成功

含有两个类的 jar 包——通过调用输出 hello

最终生成的 jar 包结构


META-INFTom.classHello.class
复制代码


方法步骤


  1. 用记事本写一个 Hello.java 和一个 Tom.java 的文件,目的是让 Hello 调用 Tom 的 speak 方法


 class Hello{     public static void main(String[] agrs){         Tom.speak();     } } class Tom{     public static void speak(){         System.out.println("hello");     } }
复制代码


  1. 编译: javac Hello.java


此时 Hello.java 和 Tom.java 同时被编译,因为 Hello 中调用了 Tom,在编译 Hello 的过程中发现还需要编译 Tom


  1. 打 jar 包,这次我们换一种方式直接定义 Main-Class。


Manifest-Version: 1.0Created-By: 1.8.0_121 (Oracle Corporation)Main-Class: Hello
复制代码


事先准备好上述的 MANIFEST.MF 文件,并存放在 META-INF 文件夹下,此时打 jar 包的命令如下


jar -cvfm hello.jar META-INF\MANIFEST.MF Hello.class Tom.class 
复制代码


  • 该命令表示用第一个文件当做 MANIFEST.MF 文件,hello.jar 作为名称,将 Hello.class 和 Tom.class 打成 jar 包

  • 其中多了一个参数 m,表示要定义 MANIFEST 文件


  1. 运行 java -jar hello.jar ,此时成功在控制台看到 hello ,成功

有目录结构的 jar 包——通过引包并调用输出 hello

最终生成的 jar 包结构


META-INFcom Tom.classHello.class
复制代码


我们将上一个稍稍变化一下,将 Tom 这个类放在 com 包下,源文件目录结构变成


com  Tom.javaHello.java
复制代码


同时 Tom.java 需要在第一行声明自己的包名


package com;


Hello.java 需要引入 Tom 这个类,同样要在第一行进行 import


import com.Tom;


方法步骤


  1. 编译 Hello.java

  2. 打 jar 包,同样准备好 MANIFEST 文件


 jar -cvfm hello.jar META-INF\MANIFEST.MF Hello.class com
复制代码


注意,最后一个 com 表示把 com 这个文件夹下的所有文件都打进 jar 包


  1. 运行 java -jar hello.jar ,此时成功在控制台看到 hello ,成功

  2. 优化过程


com 包下是有 Tom.java 源文件的,也被打进了 jar 包里,这样不太好,能不能优化一下 javac 命令,使所有的编译后文件编译到另一个隔离的地方呢,答案是可以的


在编译 Hello.java 时,先新建一个 target 文件夹。然后我们用如下命令:


javac Hello.java -d target
复制代码


  • 该命令表示,将所有编译后的文件,都放到 target 文件夹下。

  • 将 META-INF 文件夹也复制到 target 目录下,进入这个目录,输入如下命令


jar -cvfm hello.jar META-INF\MENIFEST.MF *
复制代码


注意最后一个位置变成了*,表示把当前目录下所有文件都打在 jar 包里


至此,我们可以总结出,制作一个只含有 class 字节码文件的 jar 包,以下命令足以


javac 要编译的文件 -d 目标位置


jar -cvfm 命名 MANIFEST 文件 class 类文件 1 class 类文件 2

制作含有 jar 文件的 jar 包

我们将场景稍稍变得复杂一点,看看 jar 包中需要引入其他 jar 包的场景


  1. 两个 jar 包间相互调用——调用 jar 外的 jar 输出 hello


最终生成的 jar 包结构


hello.jartom.jar
复制代码


方法步骤


准备:将上述一中写好的那个不带包的 tom.jar 复制过来(目的是调用里面的 speak 方法)


  1. 编写一个 Hello.java 并将其编译成 Hello.class,注意,由于 Hello 里面引用了 Tom 类的 speak 方法,因此在打 jar 包时应使用-cp 参数,将 tom.jar 包引入


 javac -cp tom.jar Hello.class
复制代码


这里的 -cp 表示 -classpath,指的是把 tom.jar 加入 classpath 路径下


  1. 将 hello.class 打成 jar 包,步骤略

  2. 此时运行 java -jar 发现报错 ClassNotFoundException:Tom


原因很简单,引入 jar 包需要在 MANIFEST.MF 文件中配置一个新属性:Class-Path,路径指向你需要的所有 jar 包,现在 MANIFEST.MF 这个文件应该变成


Manifest-Version: 1.0Created-By: 1.8.0_121 (Oracle Corporation)Main-Class: HelloClass-Path: Tom.jar
复制代码


  1. 修改这个文件,再次运行,发现成功在控制台输出 hello


  • javac -cp xxx.jar 要编译的文件 -d 目标位置

  • jar -cvfm 命名 MANIFEST 文件 要打包的文件 1 要打包的文件 2

jar 包中含有 jar 包——调用 jar 内的 jar 输出 hello

最终生成的 jar 包结构


META-INFHello.classtom.jar
复制代码


  • 当项目中我们把所需要的第三方 jar 包也打进了我们自己的 jar 包中时,如果仍然按照上述操作方式,会报找不到 Class 异常。原因就是 jar 引用不到放在自己内部的 jar 包。

制作含有资源文件的 jar 包

资源文件在 jar 包内部——读取 jar 内的文件


最终生成的 jar 包结构


META-INFHello.classtext.txt
复制代码


方法步骤


  import java.io.InputStream;  import java.io.BufferedReader;  import java.io.InputStreamReader;  class Hello{      public static void main(String[] args) throws Exception{          Hello hello = new Hello();          InputStream is = hello.getClass().getResourceAsStream("text.txt");          print(is);     }
/** * 读取文件,输出里面的内容,通用方法 */ public static void print(InputStream inputStream) throws Exception { InputStreamReader reader = new InputStreamReader(inputStream, "utf-8"); BufferedReader br = new BufferedReader(reader); String s = ""; while ((s = br.readLine()) != null) System.out.println(s); inputStream.close(); } }
复制代码
资源文件在另一个 jar 包内部——读取另一个 jar 内的文件

最终生成的 jar 包结构


hello.jarresource.jar text.txt
复制代码


方法步骤


同 1 一样,只不过需要在 MANIFEST 文件中将 resource.jar 加入 classpath


  import java.io.InputStream;  import java.io.BufferedReader;  import java.io.InputStreamReader;    class Hello{      public static void main(String[] args) throws Exception{          Hello hello = new Hello();          InputStream is = hello.getClass().getResourceAsStream("text.txt");          print(is);     }     /**      * 读取文件,输出里面的内容,通用方法      */     public static void print(InputStream inputStream) throws Exception {         InputStreamReader reader = new InputStreamReader(inputStream, "utf-8");         BufferedReader br = new BufferedReader(reader);         String s = "";         while ((s = br.readLine()) != null)             System.out.println(s);         inputStream.close();     } }
复制代码
资源文件在 jar 包外部——读取 jar 外的文件

最终生成的 jar 包结构


hello.jartext.txt
复制代码


方法步骤


  import java.io.InputStream;  import java.io.BufferedReader;  import java.io.InputStreamReader;  import java.io.FileInputStream;  class Hello{      public static void main(String[] args) throws Exception{          Hello hello = new Hello();          InputStream is = new FileInputStream("text.txt");         print(is);     }    /**      * 读取文件,输出里面的内容,通用方法      */     public static void print(InputStream inputStream) throws Exception {         InputStreamReader reader = new InputStreamReader(inputStream, "utf-8");         BufferedReader br = new BufferedReader(reader);         String s = "";         while ((s = br.readLine()) != null)             System.out.println(s);         inputStream.close();     } }
复制代码



运行 jar 包

  • 具体的方法是修改 jar 包内目录 META-INF 下的 MANIFEST.MF 文件

  • 比如有个叫做 demo.jar 的 jar 包,里面有一个拥有 main 方法的 main class:demo.mainClassName

  • jar 包中有个 MANIFEST.MF 文件,就只要在 MANIFEST.MF 里面添加如下一句话:Main-Class: demo.mainClassName

  • 然后我们可以在控制台里输入 java -jar test.jar 即可以运行这个 jar


如果这个项目需要引用其他第三方的 jar 包,在 IDE 里面以项目 jar 包的形式引用了这个叫做 some.jar 的包,当时放在项目的 lib 子目录下,最后项目打包时把这个 some.jar 也打进来了,但是用 java -jar 执行这个 test.jar 的时候报找不到 Class 异常,原因就是 jar 引用不到放在自己内部的 jar 包

classpath 参数设置方式

运行时将其加入 classpath 的方式行不行?就是在运行 jar 的同时加入 classpath 参数。


java -classpath some.jar -jar test.jar
复制代码


  • 这种方式是不行的,因为使用 classpath 指定的 jar 是由 AppClassloader(系统型类加载器)来加载,java 命令加了-jar 参数以后,AppClassloader 就只关注 test.jar 范围内的 class 了,classpath 参数失效

那该怎么引用其他的 jar 包呢?

方法一


Bootstrap Classloader 来加载这些类


我们之前在讲述类加载器的时候也介绍过,可以在运行时使用如下参数:


  • -Xbootclasspath:完全取代系统 Java classpath.最好不用。

  • -Xbootclasspath/a::在系统 class 加载后加载。一般用这个。

  • -Xbootclasspath/p: 在系统 class 加载前加载,注意使用,和系统类冲突就不好了.

  • win32 java -Xbootclasspath/a: some.jar;some2.jar; -jar test.jar

  • unix java -Xbootclasspath/a: some.jar:some2.jar: -jar test.jar


win32 系统每个 jar 用分号隔开,unix 系统下用冒号隔开


方法二


使用 Extension Classloader 来加载


你可以把需要加载的 jar 都扔到 %JRE_HOME%/lib/ext 下面,这个目录下的 jar 包会在 Bootstrap Classloader 工作完后由 Extension Classloader 来加载。非常方便,非常省心


方法三


使用 AppClassloader 来加载,不过不需要 classpath 参数了,我们在 MANIFEST.MF 中添加如下代码:


Class-Path: lib/some.jar


lib 是和 test.jar 同目录的一个子目录,test.jar 要引用的 some.jar 包就在这里面。如果有多个 jar 包需要引用的情况


Class-Path: lib/some.jar lib/some2.jar


每个单独的 jar 用空格隔开就可以了。注意使用相对路径。


另:如果 META-INF 下包含 INDEX.LIST 文件的话,可能会使 Class-Path 配置失效。INDEX.LIST 是 Jar 打包工具打包时生成的索引文件,删除对运行不产生影响

发布于: 2021 年 06 月 03 日阅读数: 67
用户头像

我们始于迷惘,终于更高水平的迷惘。 2020.03.25 加入

🏆 【酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“】 🏅 【Java技术领域,MySQL技术领域,APM全链路追踪技术及微服务、分布式方向的技术体系等】 🤝未来我们希望可以共同进步🤝

评论

发布
暂无评论
☕️【Java 技术之旅】知识盲点关于jar包的点点滴滴