1. 程式人生 > >可執行jar包的幾種打包/部署方式

可執行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     public
static 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

 yjmyzz.runnable.jar.DemoApp

程式就能跑起來了

方法二:藉助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。

參考文章: