JAVA

Jar包 完全解读

前言


作为java程序员,日常工作就是打jar包,可是对jar包的了解有多少呢?

一、什么是jar包


jar包就是 Java Archive File,顾名思义,它的应用是与 Java 息息相关的,是 Java 的一种文档格式,是一种与平台无关的文件格式,可将多个文件合成一个文件。jar 包与 zip 包非常相似——准确地说,它就是 zip 包,所以叫它文件包。jar 与 zip 唯一的区别就是在 jar 文件的内容中,包含了一个 META-INF/MANIFEST.MF 文件,该文件是在生成 jar 文件的时候自动创建的,作为jar里面的”详情单”,包含了该Jar包的版本、创建人和类搜索路径Class-Path等信息,当然如果是可执行Jar包,会包含Main-Class属性,表明Main方法入口,尤其是较为重要的Class-Path和Main-Class。

此外,值得注意的是,因为jar包主要是对class文件进行打包,而java编译生成的class文件是平台无关的,这就意味着jar包是跨平台的,所以不必关心涉及具体平台的问题。说到jar里面的文件,咱们来看看最普通的一个带有静态页面的springboot项目jar里面的内容,就会发现解压出来的jar并不简单,为了贴近实际咱们未做任何删减,可以看到有很多东西

只需要运行如下指令,就能看到jar里面的内容(调用jar指令的前提是已经配置了jdk的环境变量)

jar -tf springbootdemo-0.0.1-SNAPSHOT.jar

其中-tf 后接的jar就是我们要查看的jar。

大致看看里面的东西我们可以发现,除了.MF以及.class文件之外,jar还能打包静态资源文件如.html、.css以及.js等项目所需的一切,这也就意味着咱们能将自己的项目打成jar,即不管是web应用还是底层框架,都能打成jar包。

有的jar包是可以直接通过 java -jar 指令来执行的。我们都知道,有的类之所以能够执行,是因为它用你有main函数,该函数是程序的入口,同理,可执行的jar包中肯定是有某个.class文件提供了main函数才使得其可执行。那么问题来了,一个jar里面可能存在多个.class文件都有main函数的情况,我怎么知道该执行哪个?其实答案非常简单,就是看前面说的MANIFEST.MF里面的Main-Class属性,它会指定函数入口。

二、为什么要打jar包


当我们开发了一个程序以后,程序中有很多的类,如果需要提供给别人使用,发给对方一大堆源文件是非常不好的,因此通常需要把这些类以及相关的资源文件打包成一个 jar 包,把这个 jar 包提供给别人使用,同时提供给使用者清晰的文档。这样他人在拿到我们提供的jar之后,就能方便地进行调用。而且安全。能够对JAR文件进行数字签名,只让能够识别数字签名的用户使用里面的东西。

因此,建议大家在平时写代码搬砖的时候,注意把自己代码的通用部分抽离出来,主键积累一些通用的util类,将其逐渐模块化,最后打成jar包供自己在别的项目或者模块中使用,同时不断打磨jar里面的内容,将其做得越来越容易理解和通用,这样的好处是除了会对你的代码重构能力以及模块抽象能力有很好的帮助之外,更是一种从长期解放你的重复工作量,让你有更多的精力去做其他事情的方式,甚至当你抽象出业内足够通用的jar之后,jar包还能为你带来意想不到的利润(当然公司里该保密的东西还是得保密的)。这也是java发展得如此之好的原因,无论出于盈利或者非盈利的目的,将自己的通用工具或者框架抽取出来,打成jar包供他人调用,使得整个java生态圈变得越来越强大–几乎很多业务场景都能找到对应的jar包。

三、如何打jar包


3.1、通过jdk命令:

A:含有多个类的jar,类之间存在调用关系


先创建一个java项目,编写两个非常简单的类,Welcome.java和Teacher.jar,其中Welcome类在main函数里调用了Teacher类的静态方法greeting

Welcome.java 
package com.imooc.jardemo1; 
import com.imooc.jardemo1.impl.Teacher; 
public class Welcome { 
    public static void main(String[] args) {
        Teacher.greeting(); 
    } 
}
 
Teacher.java 
package com.imooc.jardemo1.impl; 
public class Teacher { 
    public static void greeting(){ 
        System.out.printf("Welcome!"); 
    } 
}

在命令行里,去到项目的src路径下,执行javac指令

javac com/imooc/jardemo1/Welcome.java

此时就会生成与这两个类相对应的.class字节码文件

由于jvm实际解析的是.class字节码文件而非.java文件,且jar中最好不要包含代码源文件,我们来将.class文件打个jar包,在src根目录下执行如下指令

jar -cvf welcome.jar com/imooc/jardemo1/Welcome.class com/imooc/jardemo1/impl/Teacher.class

c表示要创建一个新的jar包,v表示创建的过程中在控制台输出创建过程的一些信息,f表示给生成的jar包命名

打jar的时候,会生成一个META-INF的目录,里面有MANIFEST.MF这个清单列表。内容为:

Manifest-Version: 1.0 Created-By: 11 (Oracle Corporation)

此时生成的jar包还不能执行,因为缺少咱们先前说的Main-Class属性,导致jar被执行的时候,不知道执行哪个main函数。因此我们需要加上Main-Class,后接main函数所在类的全路径名(注意冒号之后一定要跟英文的空格,整个文件最后有一行空行)。

Manifest-Version: 1.0 Created-By: 11 (Oracle Corporation) Main-Class:
com.imooc.jardemo1.Welcome

添加完成后,重新执行指令打包,这次咱们在打包指令里多加一个参数,即多传入修改完成后的MANIFEST.MF文件

jar -cvfm welcome.jar META-INF/MANIFEST.MF com/imooc/jardemo1/Welcome.class com/imooc/jardemo1/impl/Teacher.class

其中多了一个参数m,表示要定义MANIFEST文件。之后再重新执行

java -jar welcome.jar

就会发现jar已成功执行

为了更方便,编译的时候使用:

javac com/imooc/jardemo1/Welcome.java -d target

该命令表示,将所有编译后的.class文件,都放到src/target文件夹下

再将先前修改好的META-INF文件夹整体复制或者移动到target/下,去到target目录,直接执行

jar -cvfm welcome.jar META-INF/MANIFEST.MF *

即可完成打包,注意最后一个位置变成了*,表示把当前目录下所有文件都打在jar包里。
此外,还有一种更简单的也更灵活的方式,不需要修改META-INF/MANIFEST.MF,即不需要指定main函数,而通过如下指令来动态指定

java -cp welcome.jar com.imooc.jardemo1.Welcome

其中cp表示classpath,后面接上全限的main函数所在的类即可

此种方式虽然灵活,但是由于不需要在MANIFEST.MF里面标注执行函数以及后面要将的Class-Path,需要调用方充分熟悉jar及其内部构造,否则需要在MANIFEST.MF以及相关的使用说明文档里描述清楚。

B、含有多个jar,jar之间存在调用关系


在原先的jardemo1项目里给Teacher.class打个jar,即在jardemo1/src目录下执行

javac com/imooc/jardemo1/impl/Teacher.java -d target2/

随后去到target2文件夹里将里面的信息打个jar包

jar -cvf teacher.jar *

将生成好的jar复制粘贴到jardemo2项目的lib目录底下(需要先创建好lib目录,其位于jardemo2根目录下,与src同级)

并新建一个项目jardemo2。

package com.imooc.jardemo2; 
import com.imooc.jardemo1.impl.Teacher; 
 
public class Welcome { 
    public static void main(String[] args) { 
        Teacher.greeting(); 
    } 
}

此时直接打jar包仍不可以运行,会报找不到Teacher这个类的错误,我们需要需要跟javac -cp一样,将teacher.jar加入到classpath里即可,具体做法是在MANIFEST.MF中配置Class-Path(如果是多个jar,则用英文空格隔开)。CLASSPATH是指定程序中所使用的类文件所在的位置。

Manifest-Version: 1.0 Created-By: 11 (Oracle Corporation) Main-Class:
com.imooc.jardemo2.Welcome Class-Path: ../../lib/teacher.jar

打jar包:

jar -cvfm welcome.jar META-INF/MANIFEST.MF *

之后再执行jar包,就会发现执行成功了。

3.2、SpringBoot通过maven打jar包。

  • 修改项目发布形式
<packaging>jar</packaging>
  • 配置加载第三方jar包的目录

其中如果要制作jar,需要在< plugins >中添加maven插件maven-compiler-plugin,否则在执行maven package时会提示编译时找不到导入的第三方包中相关类的,具体的代码如下:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>${maven-compiler-plugin.version}</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <skip>true</skip>
        <encoding>UTF-8</encoding>
        <compilerArguments>
            <extdirs>${project.basedir}/src/main/resources/lib</extdirs>
        </compilerArguments>
    </configuration>
</plugin>
  • 指定第三方jar包的打包路径
<!-- 主要配置:将引用的第三方 jar 包打进生成的 jar 文件的 BOOT-INF/lib 目录中 -->
<resources>
    <resource>
        <directory>src\main\resources\lib</directory>
        <targetPath>BOOT-INF\lib</targetPath>
      <!--  <includes>
            <include>**/*.jar</include>
        </includes>-->
    </resource>
    <resource>
        <directory>src/main/resources</directory>
    </resource>
</resources>
  • 执行mvn clean package命令即可生成相应jar包。这样在打包时首先执行clean,然后执行package即可完成jar包制作。