前言
由於筆者可見的未來會用到Java來撰寫專案,所以最近開始學習如何使用Java。說到學習一個新的語言,自然是直接嘗試撰寫一個專案是最快的學習方法了。在撰寫專案的同時,也嘗試使用Maven工具來學習如何封裝撰寫好的專案,並進行驗證和執行等等操作。在這個過程中,我了解到幾種不同的做法——Fat JAR、Thin JAR,並嘗試進行實作。接下來我將會分享這兩種做法的差異,並將專案打包成可執行的程式。
什麼是Fat JAR? Thin JAR又是什麼?
所謂的Fat JAR(也稱Uber-Jar),指的就是把所有需要的功能以及類別等等物件,通通塞進一個JAR檔案裡面。由於這樣做將會導致單一JAR檔案非常的大,因而將其稱為Fat JAR。反之,Thin JAR指的就是只有將編譯過的核心物件放入JAR檔案中,其餘依賴工具(Dependencies)等等則放置在其他地方。這樣做可以最小化JAR檔案的大小,所以稱為Thin JAR。
這兩種做法各有其好處。Fat JAR由於所有的東西都集中在一個檔案,在管理以及部署上可以相對簡單;而Thin JAR則是可以達成最有效的空間利用,且在儲存上具有更大的優勢,缺點就是管理上會需要花更多的心思。至於應該選擇何者?我想這就是根據狀況來進行決定,沒有什麼絕對的答案。
情景與實作
在本次分享中,我們將嘗試使用Java撰寫Telegram機器人,並將撰寫的程式打包成JAR進行後續使用。我們使用Maven提供的maven-archetype-quick
產生專案資料夾。以下是我們的資料夾結構:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| telegram_bot
├── dependency-reduced-pom.xml
├── pom.xml
├── src
│ ├── main
│ │ └── java
│ │ └── com
│ │ └── telegram_bot
│ │ ├── Main.java
│ │ └── Bot.java
│ └── test
│ └── java
│ └── com
│ └── telegram_bot
│ └── AppTest.java
|
首先,我們要在pom.xml
檔案中的dependencies
章節中宣告我們要使用的依賴工具:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| <dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-longpolling</artifactId>
<version>8.3.0</version>
</dependency>
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-client</artifactId>
<version>8.3.0</version>
</dependency>
</dependencies>
|
接下來,我們在Main.java
中定義了機器人的主要程式入口(Main Class):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| package com.telegram_bot;
import org.telegram.telegrambots.longpolling.TelegramBotsLongPollingApplication;
public class Main {
public static void main(String[] args) {
String botToken = "<bot token>";
try (TelegramBotsLongPollingApplication botsApplication = new TelegramBotsLongPollingApplication()) {
botsApplication.registerBot(botToken, new Bot(botToken));
System.out.println("Bot successfully started!");
Thread.currentThread().join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
接下來,我們在Bot.java
中定義繼承了LongPollingSingleThreadUpdateConsumer
的Bot
物件,這個物件會把用戶傳送的訊息原封不動回傳給用戶。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| package com.telegram_bot;
import org.telegram.telegrambots.client.okhttp.OkHttpTelegramClient;
import org.telegram.telegrambots.longpolling.util.LongPollingSingleThreadUpdateConsumer;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import org.telegram.telegrambots.meta.generics.TelegramClient;
public class Bot implements LongPollingSingleThreadUpdateConsumer {
private final TelegramClient telegramClient;
public Bot(String botToken) {
telegramClient = new OkHttpTelegramClient(botToken);
}
@Override
public void consume(Update update) {
// We check if the update has a message and the message has text
if (update.hasMessage() && update.getMessage().hasText()) {
// Set variables
String message_text = update.getMessage().getText();
long chat_id = update.getMessage().getChatId();
SendMessage message = SendMessage // Create a message object
.builder()
.chatId(chat_id)
.text(message_text)
.build();
try {
telegramClient.execute(message); // Sending our message object to user
} catch (TelegramApiException e) {
e.printStackTrace();
}
}
}
}
|
接下來,我們將示範如何在pom.xml
中進行設定,來完成Fat JAR以及Thin JAR的封裝,並將這個Bot打包成可以用的檔案。
Fat JAR
要將專案打包成Fat JAR,需要使用maven-shade-plugin
並宣告相對應的phase
、goal
、以及executions
等設定來將專案封裝成Fat JAR。首先,在pom.xml
中的plugins
中新增一個plugin
:
1
2
3
4
5
6
7
8
| <plugins>
# .... other plugins
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
</plugin>
</plugins>
|
接下來,我們在version
後面加入executions
章節。executions
章節的作用在於宣告一個執行的動作,並設定這個動作將在生命週期哪一個階段被執行,以及要做什麼。
1
2
3
4
5
6
7
8
9
10
11
12
13
| <plugins>
# .... other plugins
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</plugin>
</plugins>
|
上面新增的部分所對應的設定為:「在package
階段執行shade
的目標」。接下來我們需要使用configuration
來設定execution
時要做什麼。這邊我們定義了transformer
的動作,transformer
是為了處理資源以及Manifest時所使用的動作。在這邊我們使用了ManifestResourceTransformer
這個實作。ManifestResourceTransformer
會把我們自訂的Main Class寫到最終生成的META-INF/MANIFEST.MF
之中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| <plugins>
# .... other plugins
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.telegram_bot.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
|
在完成設定後,我們只要執行mvn clean package
,Maven就會幫我們打所有需要的東西打包成單一JAR,並放置於target/
檔案中。我們可以使用ls -alh
來檢視target/
資料夾中的檔案:
1
| -rw-r--r--@ 1 User staff 8.6M 4 Jun 23:44 telegram_bot-0.1.0.jar
|
可以注意到有一個8.6M大小的JAR檔案存在。接下來我們可以使用以下指令來運作我們的程式:
1
| java -jar target/telegram_bot-0.1.0.jar
|
執行指令後,將可以看到以下文字,代表我們的程式已經開始運作了。
1
| Bot successfully started!
|
Thin JAR
Thin JAR的做法與Fat JAR相比,產生的檔案大小相對較小,但必須要自己處理依賴工具的導入。這邊提供我學到的三個步驟,也歡迎有經驗的讀者提供更多種做法。
- 使用
mvn
指令手動將依賴工具複製到target/
資料夾,並在啟動時宣告依賴工具的位置 - 使用
AppAssembler
將所有的程式碼以及依賴工具進行搜集,並自動在產生的Manifest
資料夾產生一份依賴工具的目錄 - 使用
mvn-dependency-plugin
來自動將依賴工具打包到指定的位置
步驟一:建構基本專案
第一種方法在設定上比較簡單,只要在pom.xml
中設定好相關的依賴工具後,先使用mvn clean package
將自訂的程式碼打包,再使用以下指令來將依賴工具的程式碼複製到target/
資料夾中:
1
| mvn dependency:copy-dependencies -DoutputDirectory=target/dependency
|
接下來,在使用以下指令,在運作自己撰寫的JAR同時,宣告依賴工具的程式碼位置,來使得程式能夠正常運作:
1
| java -cp "telegram_bot-0.1.0.jar:dependency/*" com.telegram_bot.Main
|
當指令執行後,將可以看到與Fat JAR相同,出現以下訊息:
1
| Bot successfully started!
|
這種處理方式可以不需要在設定中有太多的著墨,但是後續需要手動將依賴工具複製到對應的位置,且還要在啟動時手動宣告依賴工具的位置,個人是不太喜歡的。所以就繼續研究,並了解到可以使用方法二來讓整個Thin JAR的產生及維護更加的順暢且容易管理。
步驟二:設定依賴工具位置,簡化啟動指令
在Thin JAR的打包上,我們可以透過pom.xml
中的maven-jar-plugin
所提供的功能,設定依賴工具將被放置的位置,同時設定主要入口的Class名稱。這樣一來,我們也可以直接以近乎Fat JAR的方式來運作程式,但在依賴工具管理和儲存上有更大的優勢。
要達成這個目標,首先我們要對maven-jar-plugin
進行設定:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| <plugins>
# .... other plugins
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.telegram_bot.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
|
在上方的設定中,我們宣告要將Class Path
納入產生的Manifest
中,並宣告了Class Path
的存放資料夾;此外,我們也透過mainClass
設定了程式的主要入口。這其實等同於把java -cp
指令中的參數,放入了pom.xml
之中。接下來,我們就可以執行以下的指令,來將程式進行打包,並將依賴工具放到我們指定的位置:
1
2
| mvn clean package
mvn dependency:copy-dependencies -DoutputDirectory=target/lib
|
完成後,我們就可以使用與Fat JAR相同的指令來啟動程式:
1
| java -jar target/telegram_bot-0.1.0.jar
|
這樣一來,我們就不需要為了Fat JAR以及Thin JAR分別使用不同的指令,在管理上有了一定程度的改善,但可惜的是仍沒辦法讓依賴工具的處理方面能夠被自動的處理。那麼,該怎麼做才能讓依賴工具能被自動的打包到target/
呢?
步驟三:設定依賴工具自動打包,進一步簡化流程
想要讓依賴工具的打包也被納入mvn package
的流程中,我們需要借助maven-dependency-plugin
的幫助。我們需要提供以下幾個資訊來讓maven-dependency-plugin
幫我們打這些依賴工具打包到我們希望的位置:
outputDirectory
:依賴工具的輸出資料夾includeScope
:要複製的依賴工具範圍
在這裡,我們會如同上一步驟,將依賴工具打包到target/lib/
資料夾,並限制只打包在有運作時所需要的依賴工具。以下是我們的設定:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| <plugins>
# .... other plugins
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
|
完成上述設定後,只要執行mvn clean package
指令,Maven就會自動將對應的依賴工具,連同我們撰寫的程式碼一起打包,我們再也不需要手動使用mvn
來複製依賴工具了。接下來,只需要使用相同的指令,就可以啟動我們的程式:
1
| java -jar target/telegram_bot-0.1.0.jar
|
可喜可賀,我們成功地讓打包流程變得更有效率,且避免了將所有的程式和設定都塞在同一個JAR檔案中了!
小節
這次學習Java以及Maven的過程,與過去使用其他語言的過程有許多的不同,對我來說是非常新奇的體驗。Maven提供了很多很有趣,也很強大的功能,不過在學習上所需要花費的心力也是相對更多的。希望這篇文章可以幫助到其他想接觸Java的朋友。
如果覺得我的文章對你有幫助,歡迎請我喝一杯咖啡~
