[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é)
- Java原生打包治力、Maven原生打包蒙秒、shade插件打包的結(jié)果,其結(jié)構(gòu)都是一致的宵统∷胺荆可以使用
java -cp
的方式執(zhí)行,一般無法直接使用java -jar
的方式執(zhí)行。 - 使用spring-boot插件打包益兄,其結(jié)構(gòu)和上述的結(jié)構(gòu)不同锻梳。不能使用
java -cp
的方式執(zhí)行,可以使用java -jar
的方式執(zhí)行净捅。 - shade插件會忽略provided的依賴疑枯,不集成到j(luò)ar包里;spring-boot插件會將所有的依賴都集成到j(luò)ar包里蛔六。
- 默認的情況下荆永,shade插件和spring-boot插件的打包結(jié)果,會代替Maven原生打包結(jié)果被安裝到本地倉庫(執(zhí)行
mvn install
時)国章,可以通過配置改變這一點具钥。