Java項目打包方式分析

[TOC]

概述

在項目實踐過程中问慎,有個需求需要做一個引擎能執(zhí)行指定jar包的指定main方法齿税。

起初我們以一個簡單的spring-boot項目進行測試俗批,使用spring-boot-maven-plugin進行打包,使用java -cp demo.jar <package>.<MainClass>執(zhí)行风罩,結(jié)果報錯找不到對應(yīng)的類。

我分析了spring-boot-maven-plugin打包的結(jié)構(gòu)舵稠,又回頭復(fù)習(xí)了java原生jar命令打包的結(jié)果超升,以及其他Maven打包插件打包的結(jié)果,然后寫成這邊文章哺徊。

這篇文章里會簡單介紹java原生的打包方式室琢,maven原生的打包方式,使用maven shade插件將項目打成一個大一統(tǒng)的jar包的方式落追,使用spring-boot-maven-plugin將項目打成一個大一統(tǒng)的jar包的方式研乒,并比較它們的差異,給出使用建議淋硝。

Java原生打包

為了簡單起見,假設(shè)我們的項目只有一個HelloWorld.java宽菜,不使用Maven谣膳。假設(shè)當(dāng)前目錄為.,初始目錄下沒有任何內(nèi)容铅乡。

首先继谚,我們在當(dāng)前目錄新建文件HelloWorld.java。為了演示如何讓編譯的class文件自動放置到與package對應(yīng)的目錄結(jié)構(gòu)中阵幸,特地添加package命令花履。

package com.hikvision.demo;

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

在當(dāng)前目錄新建target子目錄,此時挚赊,目錄結(jié)構(gòu)如下:

 ./
  ├─ HelloWorld.java
  ├─ target/

編譯

命令:javac HelloWorld.java -d target诡壁。目錄結(jié)構(gòu)變?yōu)椋?/p>

 ./
  ├─ HelloWorld.java
  ├─ target/
        ├─ com/hikvision/demo/
              ├─ HelloWorld.class

打包

命令:jar cvf demo-algorithm.jar -C target/ .。目錄結(jié)構(gòu)變?yōu)椋?/p>

 ./
  ├─ HelloWorld.java
  ├─ target/
  │     └─ com/hikvision/demo/
  │           └─ HelloWorld.class
  ├─ demo-algorithm.jar

打包的結(jié)果demo-algorithm.jar荠割,其內(nèi)部結(jié)構(gòu)為:

demo-algorithm.jar
  ├─ com
  │   └─ hikvision
  │       └─ demo
  │           └─ HelloWorld.class
  └─ META-INF
      └─ MANIFEST.MF

其中妹卿,MANIFEST.MF的內(nèi)容為:

Manifest-Version: 1.0
Created-By: 1.8.0_144 (Oracle Corporation)

運行

命令:java -cp demo-algorithm.jar com.hikvision.demo.HelloWorld

留意上面的jar包的結(jié)構(gòu)蔑鹦,如果我們希望以java -cp的方式運行jar包中的某一個類的main方法夺克,class的package必須對應(yīng)jar包內(nèi)部的一級目錄。

這種結(jié)構(gòu)我們稱之為java標(biāo)準jar包結(jié)構(gòu)嚎朽。

Maven原生打包

我一般使用mvn clean package命令打包铺纽。

maven打包的結(jié)果,jar包名稱是根據(jù)artifactId和version來生成的哟忍,比如對于com.hikvision.algorithm:demo-algorithm:0.0.1-SNAPSHOT的打包結(jié)果是:demo-algorithm-0.0.1-SNAPSHOT.jar狡门。

分析這個jar包的結(jié)構(gòu):

.
├─com
│  └─hikvision
│      └─algorithm
│          └─HelloWorld.class
├─META-INF
│   ├─maven
│   │   └─com.hikvision.algorithm
│   │       └─demo-algorithm
│   │           ├─pom.properties
│   │           └─pom.xml
│   └─MANIFEST.MF
└─application.properties

除META-INF目錄之外陷寝,其他的都是class path,這一點符合java標(biāo)準jar結(jié)構(gòu)融撞。不同的是META-INF有一級子目錄maven盼铁,放置項目的maven信息。

對于maven原生的打包結(jié)果尝偎,可以使用java -cp的方式執(zhí)行其中某個主類饶火。但是需要注意它并沒有包含所依賴的jar包,這需要另外提供致扯。

使用Maven shade插件打包

Maven打包插件應(yīng)該不止一種肤寝,這里使用的是maven-shade-plugin

在pom文件中添加插件配置:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>2.4.3</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
        </execution>
    </executions>
</plugin>

根據(jù)上面的配置抖僵,在package階段鲤看,會自動執(zhí)行插件的shade目標(biāo),這個目標(biāo)負責(zé)將項目的class文件耍群,以及項目依賴的class文件都會統(tǒng)一打到一個jar包里义桂。

我們可以執(zhí)行mvn clean package來自動觸發(fā)shade,或者直接執(zhí)行mvn shade:shade蹈垢。

target目錄會生成2個jar包慷吊,一個是maven原生的jar包,一個是插件的jar包:

target/
 ├─ original-demo-algorithm-0.0.1-SNAPSHOT.jar (4KB)
 └─ demo-algorithm-0.0.1-SNAPSHOT.jar (6.24MB)

original-demo-algorithm-0.0.1-SNAPSHOT.jar是原生的jar包曹抬,不包含任何依賴溉瓶,只有4KB。demo-algorithm-0.0.1-SNAPSHOT.jar是包含依賴的jar包谤民,有6.24MB堰酿。

對照上文可以猜測shade插件對maven原生打包結(jié)果進行重命名之后,使用這個名字又打出一個集成了依賴的jar包张足。

注意触创,這表示如果執(zhí)行了mvn install,最終被安裝到本地倉庫的是插件打出的jar包为牍,而不是maven原生的打包結(jié)果嗅榕。可以配置插件吵聪,修改打包結(jié)果的名稱:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>2.4.3</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <finalName>demo-algorithm-0.0.1-SNAPSHOT-assembly</finalName>
            </configuration>
        </execution>
    </executions>
</plugin>

使用這個配置凌那,最終的打包結(jié)果:

target/
 ├─ demo-algorithm-0.0.1-SNAPSHOT.jar (4KB)
 └─ demo-algorithm-0.0.1-SNAPSHOT-assembly.jar (6.24MB)

此時,demo-algorithm-0.0.1-SNAPSHOT.jar是maven原生的打包結(jié)果吟逝,demo-algorithm-0.0.1-SNAPSHOT-assembly.jar是插件的打包結(jié)果帽蝶。

插件打包結(jié)果的內(nèi)部結(jié)構(gòu)如下:

├─ch
│  └─qos
│      └─logback
│          ├─classic
│          │  ├─boolex
│          │  ├─db
│          │  │  ├─names
│          │  │  └─script
│          │  ├─encoder
│          │  └─util
│          └─core
│              ├─boolex
│              ├─db
│              │  └─dialect
│              ├─encoder
│              ├─joran
│              │  ├─action
│              │  ├─conditional
│              │  ├─event
│              │  │  └─stax
│              │  ├─node
│              │  ├─spi
│              │  └─util
│              │      └─beans
│              ├─subst
│              └─util
├─com
│  └─hikvision
│      └─algorithm
├─META-INF
│  ├─maven
│  │  ├─ch.qos.logback
│  │  │  ├─logback-classic
│  │  │  └─logback-core
│  │  ├─com.hikvision.algorithm
│  │  │  └─demo-algorithm
│  │  ├─org.slf4j
│  │  │  ├─jcl-over-slf4j
│  │  │  ├─jul-to-slf4j
│  │  │  ├─log4j-over-slf4j
│  │  │  └─slf4j-api
│  │  ├─org.springframework.boot
│  │  │  ├─spring-boot
│  │  │  ├─spring-boot-autoconfigure
│  │  │  ├─spring-boot-starter
│  │  │  └─spring-boot-starter-logging
│  │  └─org.yaml
│  │      └─snakeyaml
│  ├─org
│  │  └─apache
│  │      └─logging
│  │          └─log4j
│  │              └─core
│  │                  └─config
│  │                      └─plugins
│  └─services
└─org
    ├─apache
    │  ├─commons
    │  │  └─logging
    │  │      └─impl
    │  └─log4j
    │      ├─helpers
    │      ├─spi
    │      └─xml
    ├─slf4j
    │  ├─bridge
    │  ├─event
    │  ├─helpers
    │  ├─impl
    │  └─spi
    ├─springframework
    │  ├─boot
    │  │  ├─admin
    │  │  ├─ansi
    │  │  ├─web
    │  │  │  ├─client
    │  │  │  ├─filter
    │  │  │  ├─servlet
    │  │  │  └─support
    │  │  └─yaml
    │  └─validation
    │      ├─annotation
    │      ├─beanvalidation
    │      └─support
    └─yaml
        └─snakeyaml
            ├─error
            ├─tokens
            └─util

這里省略了所有的文件,以及大部分的子目錄。

META-INF目錄外的其他所有目錄励稳,都是classpath佃乘,結(jié)構(gòu)和Maven原生的打包結(jié)構(gòu)相同。不同的是shade插件將所有的依賴jar解壓縮之后驹尼,和項目的class文件一起重新打成jar包趣避;并且在META-INF/maven下包含了項目本身及所依賴的項目的pom信息。

如果在pom文件中新翎,聲明某個依賴是provided的程帕,它就不會被集成到j(luò)ar包里。

總的來說地啰,使用maven-shade-plugin打出的jar包的結(jié)構(gòu)依然符合java標(biāo)準jar包結(jié)構(gòu)愁拭,所以我們可以通過java -cp的方式運行jar包中的某一個類的main方法。

使用spring-boot-maven-plugin插件打包

項目首先必須是spring-boot項目亏吝,即項目直接或間接繼承了org.springframework.boot:spring-boot-starter-parent岭埠。

在pom文件中配置spring-boot-maven-plugin插件:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

這個插件默認將打包綁定在了maven生命周期的package階段,即執(zhí)行package命令會自動觸發(fā)插件打包蔚鸥。

插件會將Maven原生的打包結(jié)果重命名惜论,然后將自己的打包結(jié)果使用之前那個名字。比如:

target/
  ├─ ...
  ├─ demo-algorithm-0.0.1-SNAPSHOT.jar.original
  └─ demo-algorithm-0.0.1-SNAPSHOT.jar

如上止喷,demo-algorithm-0.0.1-SNAPSHOT.jar.original是Maven原生的打包結(jié)果馆类,被重命名之后追加了.original后綴。demo-algorithm-0.0.1-SNAPSHOT.jar是插件的打包結(jié)果启盛。

這里需要注意,如果運行了mvn install技羔,會將這個大一統(tǒng)的jar包安裝到本地倉庫僵闯。這一點可以配置,使用下面的插件配置藤滥,可以確保安裝到本地倉庫的是原生的打包結(jié)果:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <!--將原始的包作為install和deploy的對象鳖粟,而不是包含了依賴的包-->
        <attach>false</attach>
    </configuration>
</plugin>

spring-boot-maven-plugin打包的結(jié)構(gòu)如下:

.
├─BOOT-INF
│  ├─classes
│  │  └─com
│  │      └─hikvision
│  │          └─algorithm
│  └─lib
├─META-INF
│  └─maven
│      └─com.hikvision.algorithm
│          └─demo-algorithm
└─org
    └─springframework
        └─boot
            └─loader
                ├─archive
                ├─data
                ├─jar
                └─util

這里忽略了所有的文件。

分析這個結(jié)構(gòu)拙绊,spring-boot插件將項目本身的class放到了目錄BOOT-INF/classes下向图,將所有依賴的jar放到了BOOT-INF/lib下。在jar包的頂層有一個子目錄org标沪,是spring-boot loader相關(guān)的classes榄攀。

所以,這個與java標(biāo)準jar包結(jié)構(gòu)是不同的金句,和maven原生的打包結(jié)構(gòu)也是不同的檩赢。

另外,需要注意的是违寞,即使設(shè)置為provided的依賴贞瞒,依然會被集成到j(luò)ar包里偶房,這一點與上文的shade插件不同。

分析META-INF/MANIFEST.MF文件內(nèi)容:

Manifest-Version: 1.0
Implementation-Title: demo-algorithm
Implementation-Version: 0.0.1-SNAPSHOT
Archiver-Version: Plexus Archiver
Built-By: lijinlong9
Implementation-Vendor-Id: com.hikvision.algorithm
Spring-Boot-Version: 1.5.8.RELEASE
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.hikvision.algorithm.HelloWorld
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_144
Implementation-URL: http://projects.spring.io/spring-boot/demo-algorithm/

注意军浆,這里配置了Main-Class棕洋,這表示我們可以以java -jar的方式執(zhí)行這個jar包。Main-Class對應(yīng)的值為org.springframework.boot.loader.JarLauncher乒融,這表示具體的加載過程是由spring-boot定義的掰盘。

這里有一篇文章分析spring boot
jar的啟動過程
。我簡單看了下這篇文章簇抵,并沒有細讀庆杜,我大概猜測到spring-boot實現(xiàn)了一套自己的加載機制,與這個機制相對應(yīng)的碟摆,spring-boot也自定義了一套自己的jar包結(jié)構(gòu)晃财。對我這說,目前了解到這個程度就夠了典蜕。

因為不符合Java標(biāo)準jar包結(jié)構(gòu)断盛,所以無法通過java -cp <package>.<MainClass>的方式運行jar包里的某個類,因為按照標(biāo)準的jar包結(jié)構(gòu)是找不到這個類的愉舔。

從這一點來看钢猛,我們需要重新思考什么樣的項目或者module應(yīng)該做成spring-boot項目?到目前為止轩缤,我認為只有完整命迈、可運行的項目或module才需要做成spring-boot項目,比如對外提供rest服務(wù)的module火的。而像common類的module壶愤,對外提供公共類庫,其本身無法獨立運行馏鹤,則不應(yīng)該作為spring-boot項目征椒。

更何況對于多module的項目,將最頂層的module定義為spring-boot項目湃累,而讓所有的子module都通過繼承頂層module來間接繼承spring-boot-starter-parent的做法勃救,應(yīng)該是大謬的吧。

總結(jié)

  1. Java原生打包治力、Maven原生打包蒙秒、shade插件打包的結(jié)果,其結(jié)構(gòu)都是一致的宵统∷胺荆可以使用java -cp的方式執(zhí)行,一般無法直接使用java -jar的方式執(zhí)行。
  2. 使用spring-boot插件打包益兄,其結(jié)構(gòu)和上述的結(jié)構(gòu)不同锻梳。不能使用java -cp的方式執(zhí)行,可以使用java -jar的方式執(zhí)行净捅。
  3. shade插件會忽略provided的依賴疑枯,不集成到j(luò)ar包里;spring-boot插件會將所有的依賴都集成到j(luò)ar包里蛔六。
  4. 默認的情況下荆永,shade插件和spring-boot插件的打包結(jié)果,會代替Maven原生打包結(jié)果被安裝到本地倉庫(執(zhí)行mvn install時)国章,可以通過配置改變這一點具钥。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市液兽,隨后出現(xiàn)的幾起案子骂删,更是在濱河造成了極大的恐慌,老刑警劉巖四啰,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宁玫,死亡現(xiàn)場離奇詭異,居然都是意外死亡柑晒,警方通過查閱死者的電腦和手機欧瘪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匙赞,“玉大人佛掖,你說我怎么就攤上這事∮客ィ” “怎么了芥被?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長脾猛。 經(jīng)常有香客問我撕彤,道長鱼鸠,這世上最難降的妖魔是什么猛拴? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮蚀狰,結(jié)果婚禮上愉昆,老公的妹妹穿的比我還像新娘。我一直安慰自己麻蹋,他們只是感情好跛溉,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般芳室。 火紅的嫁衣襯著肌膚如雪专肪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天堪侯,我揣著相機與錄音嚎尤,去河邊找鬼。 笑死伍宦,一個胖子當(dāng)著我的面吹牛芽死,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播次洼,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼关贵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了卖毁?” 一聲冷哼從身側(cè)響起揖曾,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎势篡,沒想到半個月后翩肌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡禁悠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年念祭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碍侦。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡粱坤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瓷产,到底是詐尸還是另有隱情站玄,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布濒旦,位于F島的核電站株旷,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏尔邓。R本人自食惡果不足惜晾剖,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望梯嗽。 院中可真熱鬧齿尽,春花似錦、人聲如沸灯节。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至卡骂,卻和暖如春国裳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背全跨。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工躏救, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人螟蒸。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓盒使,卻偏偏與公主長得像,于是被迫代替她去往敵國和親七嫌。 傳聞我的和親對象是個殘疾皇子少办,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345