可執行jar包的幾種打包/部署方式
java專案開發中,最終生成的jar,大概可分為二類,一類是一些通用的工具類(不包含main入口方法),另一類是可直接執行的jar包(有main入口方法),下面主要講的是後者,要讓一個jar檔案可直接執行,通常有幾下三種方式:
動工之前,先搭一個專案架子便於後面分析:
專案結構圖:
這是一個gradle專案(當然,也可以換成ant專案或maven專案,這不重要),裡面有二個模組,my-jar的DemoApp裡提供了main入口方法,通常一個可執行的java專案,都會依賴其它一些jar包,所以另一個模組my-lib模擬工具類的jar包,即:簡單來說,my-jar依賴my-lib。
my-lib中DemoLib類的原始碼:
1 package yjmyzz.runnable.lib; 2 3 public class DemoLib { 4 5 public static void demo() { 6 System.out.println("我是DemoLib中的demo方法"); 7 } 8 }
my-jar中DemoApp類的原始碼:
1 package yjmyzz.runnable.jar; 2 3 import yjmyzz.runnable.lib.DemoLib; 4 5 public class DemoApp { 6 7 publicstatic void main(String[] args) { 8 DemoLib.demo(); 9 } 10 }
二個專案編譯後,會得到二個jar包:my-jar.jar及my-lib.jar
方法一:不借助manifest檔案
java -classpath jar1:jar2:jar3... mainClassName
解解一下:
紅色的是固定部分,中間藍色的是jar包的路徑(多個jar之間用:號連線),最後綠色的部分是main方法所在的類名,按這個思路
把這二個jar包扔同一個目錄下,輸入如下命令:
java -classpath my-jar.jar:my-lib.jar
程式就能跑起來了
方法二:藉助manifest檔案
想辦法在my-jar.jar中新增MANIFEST.MF檔案,內容參考下面這樣:
Main-Class: yjmyzz.runnable.jar.DemoApp
Class-Path: my-lib.jar
同樣,將這二個jar包扔在一起,然後
java -jar my-jar.jar 就能運行了,至於如何在打包裡,自動新增MANIFEST.MF檔案,gradle下可以這麼做:
jar { manifest { attributes 'Main-Class': 'yjmyzz.runnable.jar.DemoApp' attributes 'Class-Path': 'my-lib.jar' } }
方法三:藉助spring-boot 外掛
前面二種方法,主程式的jar包,與依賴的jar包是分開的,這在雲環境中,上傳部署比較麻煩,得傳多個檔案(或者上傳前,先壓縮成一個包,再傳到伺服器上解壓),伺服器節點多時,操作起來太累。又到我大Spring出場了,將my-jar專案中的build.gradle改成下面這樣:
apply plugin: 'java' apply plugin: 'spring-boot' buildscript { repositories { maven { url 'http://maven.oschina.net/content/groups/public/' } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.0.RELEASE") } } repositories { maven { url 'http://maven.oschina.net/content/groups/public/' } } dependencies { compile project(':my-lib') }
然後命令列輸入 gradle bootRepackage 將在build/libs下生成二個檔案my-jar.jar以及my-jar.jar.original(先不用管這個original檔案是啥)
直接java -jar my-jar.jar 就能運行了(注意:這種方式下,並不需要my-lib.jar這類依賴的jar檔案),其原理在於spring-boot外掛把所有依賴的jar包,全都打到一個jar包裡了。
基本上,到這裡這篇文章就可以完結了,如果有同學對spring-boot這種打包方式比較好奇,想深入研究,可以繼續向下看,把my-jar.jar.original改名為my-jar-origin.jar,然後輸入jar tf my-jar-origin.jar 即顯示這個jar包的內容,會得到以下輸出:
META-INF/ META-INF/MANIFEST.MF yjmyzz/ yjmyzz/runnable/ yjmyzz/runnable/jar/ yjmyzz/runnable/jar/DemoApp.class
即.original檔案,其實就是一個普通的jar包,其中的MANIFEST.MF並沒有什麼實質性內容,只是一個空殼,這樣的jar包,跟方法1得到的jar包是一樣的。
再輸入jar tf my-jar.jar,會得到以下輸出:
1 META-INF/ 2 META-INF/MANIFEST.MF 3 yjmyzz/ 4 yjmyzz/runnable/ 5 yjmyzz/runnable/jar/ 6 yjmyzz/runnable/jar/DemoApp.class 7 lib/ 8 lib/my-lib.jar 9 org/ 10 org/springframework/ 11 org/springframework/boot/ 12 org/springframework/boot/loader/ 13 org/springframework/boot/loader/LaunchedURLClassLoader$Java7LockProvider.class 14 org/springframework/boot/loader/PropertiesLauncher$ArchiveEntryFilter.class 15 org/springframework/boot/loader/PropertiesLauncher$PrefixMatchingArchiveFilter.class 16 org/springframework/boot/loader/ExecutableArchiveLauncher$1.class 17 org/springframework/boot/loader/PropertiesLauncher.class 18 org/springframework/boot/loader/LaunchedURLClassLoader$ResourceEnumeration.class 19 org/springframework/boot/loader/data/ 20 org/springframework/boot/loader/data/ByteArrayRandomAccessData.class 21 org/springframework/boot/loader/data/RandomAccessDataFile$DataInputStream.class 22 org/springframework/boot/loader/data/RandomAccessData.class 23 org/springframework/boot/loader/data/RandomAccessDataFile$FilePool.class 24 org/springframework/boot/loader/data/RandomAccessDataFile.class 25 org/springframework/boot/loader/data/RandomAccessData$ResourceAccess.class 26 org/springframework/boot/loader/util/ 27 org/springframework/boot/loader/util/SystemPropertyUtils.class 28 org/springframework/boot/loader/util/AsciiBytes.class 29 org/springframework/boot/loader/LaunchedURLClassLoader$1.class 30 org/springframework/boot/loader/InputArgumentsJavaAgentDetector.class 31 org/springframework/boot/loader/Launcher.class 32 org/springframework/boot/loader/LaunchedURLClassLoader.class 33 org/springframework/boot/loader/JarLauncher.class 34 org/springframework/boot/loader/jar/ 35 org/springframework/boot/loader/jar/JarEntryFilter.class 36 org/springframework/boot/loader/jar/JarURLConnection.class 37 org/springframework/boot/loader/jar/JarEntry.class 38 org/springframework/boot/loader/jar/Bytes.class 39 org/springframework/boot/loader/jar/CentralDirectoryEndRecord.class 40 org/springframework/boot/loader/jar/JarFile$2.class 41 org/springframework/boot/loader/jar/ZipInflaterInputStream.class 42 org/springframework/boot/loader/jar/JarFile.class 43 org/springframework/boot/loader/jar/JarFile$1.class 44 org/springframework/boot/loader/jar/JarURLConnection$1.class 45 org/springframework/boot/loader/jar/Handler.class 46 org/springframework/boot/loader/jar/JarURLConnection$JarEntryName.class 47 org/springframework/boot/loader/jar/JarEntryData.class 48 org/springframework/boot/loader/MainMethodRunner.class 49 org/springframework/boot/loader/InputArgumentsJavaAgentDetector$1.class 50 org/springframework/boot/loader/WarLauncher.class 51 org/springframework/boot/loader/PropertiesLauncher$1.class 52 org/springframework/boot/loader/ExecutableArchiveLauncher.class 53 org/springframework/boot/loader/LaunchedURLClassLoader$LockProvider.class 54 org/springframework/boot/loader/archive/ 55 org/springframework/boot/loader/archive/JarFileArchive$JarFileEntry.class 56 org/springframework/boot/loader/archive/JarFileArchive.class 57 org/springframework/boot/loader/archive/FilteredArchive.class 58 org/springframework/boot/loader/archive/JarFileArchive$1.class 59 org/springframework/boot/loader/archive/ExplodedArchive.class 60 org/springframework/boot/loader/archive/FilteredArchive$2.class 61 org/springframework/boot/loader/archive/Archive$Entry.class 62 org/springframework/boot/loader/archive/ExplodedArchive$1.class 63 org/springframework/boot/loader/archive/Archive$EntryFilter.class 64 org/springframework/boot/loader/archive/FilteredArchive$1.class 65 org/springframework/boot/loader/archive/ExplodedArchive$FileEntry.class 66 org/springframework/boot/loader/archive/Archive.class 67 org/springframework/boot/loader/archive/Archive$EntryRenameFilter.class 68 org/springframework/boot/loader/archive/ExplodedArchive$FilteredURLStreamHandler.class 69 org/springframework/boot/loader/archive/ExplodedArchive$FileNotFoundURLConnection.class 70 org/springframework/boot/loader/JavaAgentDetector.class
很明顯,多出了很多內容,注意第8行,可以發現my-lib.jar這個依賴的jar包,已經打包到my-jar.jar內部了,這時的MANIFEST.MF內容為:
1 Manifest-Version: 1.0 2 Start-Class: yjmyzz.runnable.jar.DemoApp 3 Spring-Boot-Version: 1.3.0.RELEASE 4 Main-Class: org.springframework.boot.loader.JarLauncher
Main-Class被設定成org.springframework.boot.loader.JarLauncher,此外還增加了Start-Class指向我們真正的程式入口yjmyzz.runnable.jar.DemoApp,換句話說,程式執行時,先呼叫org.springframework.boot.loader.JarLauncher,然後找到Start-Class對應的類,最終執行,執行過程中,會查詢內部lib下的依賴jar包my-lib.jar,當然這一切是需要有額外的程式碼來處理的,所以多出來的org/springframework/boot下的一堆class,就是spring用來幹這件事兒的。
第三種方式,很適合雲環境的部署,只需要扔一個jar包上去就完事了,這種all-in-one的jar包,也被換為fat-jar。
參考文章: