芋道 Spring Boot 熱部署入門

摘要: 原創出處 http://www.iocoder.cn/Spring-Boot/hot-swap/ 「芋道源碼」歡迎轉載,保留摘要,謝謝!

  • 1. 概述
  • 2. spring-boot-devtools
  • 3. IDEA 熱部署
  • 4. Jrebel
  • 666. 彩蛋

1. 概述

在日常開發中,我們需要經常修改 Java 程式碼,手動重啟項目,查看修改後的效果。如果在項目小時,重啟速度比較快,等待的時間是較短的。但是隨著項目逐漸變大,重啟的速度變慢,等待時間 1-2 min 是比較常見的。

這樣就導致我們開發效率降低,影響我們的下班時間,哈哈哈~那麼是否有方式能夠實現,在我們修改完 Java 程式碼之後,能夠不重啟項目呢?

答案是有的,通過熱部署的方式。並且實現的方式還是非常多,艿艿在本文就會為胖友一一展示。

旁白君:嚴格來說,應該叫 HotSwap 的方式,翻譯成中文會有熱部署、熱更新、熱替換、熱載入等等多種。這裡,我們就採用大家可能說的比較多的翻譯,熱部署。

為了演示方便,胖友可以參考 lab-48-demo 項目,搭建一個簡單的 Spring Boot 項目,提供了一個簡單的 HTTP API。如下圖所示:

友情提示:不要直接通過克隆 https://github.com/YunaiV/SpringBoot-Labs 來使用該項目,實在太大了!

並且,我們下面我們所有的演示,都是在宇宙無敵 Java 開發工具 IDEA 中進行。

2. spring-boot-devtools

spring-boot-devtools 是 Spring Boot 提供的開發者工具,它會監控當前應用所在的 classpath 下的文件發生變化,進行自動重啟。

注意,spring-boot-devtools 並沒有採用熱部署的方式,而是一種較快的重啟方式。其官方文檔解釋如下:

FROM 《Spring Boot 2.X 中文文檔 —— 開發者工具》 Spring Boot 通過使用兩個類載入器來提供了重啟技術。

  • 不改變的類(例如,第三方 jar)被載入到 base 類載入器中。
  • 經常處於開發狀態的類被載入到 restart 類載入器中。

當應用重啟時,restart 類載入器將被丟棄,並重新創建一個新的。這種方式意味著應用重啟比冷啟動要快得多,因為省去 base 類載入器的處理步驟,並且可以直接使用。 如果您覺得重啟還不夠快,或者遇到類載入問題,您可以考慮如 ZeroTurnaround 的 JRebel 等工具。他們是通過在載入類時重寫類來加快重新載入。

在項目中,我們需要在 pom.xml 中,引入 spring-boot-devtools 依賴如下:

<dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-devtools</artifactId>      <optional>true</optional> <!-- 可選 -->  </dependency>  

2.1 演示

下面,我們來演示下 spring-boot-devtools 的使用。

① Run 或者 Debug 運行 Spring Boot 應用。

使用瀏覽器,訪問 http://127.0.0.1:8080/demo/echo 介面,返回結果為 "echo"。

② 修改 DemoController 的 #echo() 方法,設置返回值為 "none"。

【關鍵】 我們現在僅僅需要修改了 Java 程式碼,需要重新編譯下程式碼。點擊 IDEA 的菜單 Build -> Build Project,手動進行編譯。如下圖所示:

友情提示:如果胖友嫌棄滑鼠操作太慢,可以使用 Build Project 的快捷鍵:

  • Mac:Command + F9
  • Windows:Ctrl + F9

此時,IDEA 控制台會看到 Spring Boot 重新啟動的日誌如下:

2020-02-09 09:22:52.082  INFO 36495 --- [      Thread-10] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'      .   ____          _            __ _ _   /\ / ___'_ __ _ _(_)_ __  __ _      ( ( )___ | '_ | '_| | '_ / _` |       \/  ___)| |_)| | | | | || (_| |  ) ) ) )    '  |____| .__|_| |_|_| |___, | / / / /   =========|_|==============|___/=/_/_/_/   :: Spring Boot ::        (v2.2.4.RELEASE)    2020-02-09 09:22:52.195  INFO 36495 --- [  restartedMain] cn.iocoder.demo03.Demo03Application      : Starting Demo03Application on MacBook-Pro-8 with PID 36495 (/Users/yunai/Downloads/demo03/target/classes started by yunai in /Users/yunai/Downloads/demo03)  2020-02-09 09:22:52.195  INFO 36495 --- [  restartedMain] cn.iocoder.demo03.Demo03Application      : No active profile set, falling back to default profiles: default  2020-02-09 09:22:52.335  INFO 36495 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)  2020-02-09 09:22:52.336  INFO 36495 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]  2020-02-09 09:22:52.336  INFO 36495 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.30]  2020-02-09 09:22:52.342  INFO 36495 --- [  restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext  2020-02-09 09:22:52.342  INFO 36495 --- [  restartedMain] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 145 ms  2020-02-09 09:22:52.382  INFO 36495 --- [  restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'  2020-02-09 09:22:52.409  INFO 36495 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729  2020-02-09 09:22:52.418  INFO 36495 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''  2020-02-09 09:22:52.419  INFO 36495 --- [  restartedMain] cn.iocoder.demo03.Demo03Application      : Started Demo03Application in 0.244 seconds (JVM running for 169.162)  2020-02-09 09:22:52.420  INFO 36495 --- [  restartedMain] .ConditionEvaluationDeltaLoggingListener : Condition evaluation unchanged  
  • ? 所以 spring-boot-devtools 真的不是熱部署,而是更快的重啟方式。

使用瀏覽器,再次訪問 http://127.0.0.1:8080/demo/echo 介面,返回結果為 "none",成功!

咳咳咳,下面我們來講解下自動編譯。艿艿自己嘗試了,親測失敗。例如說 《IDEA 配置 Spring Boot 熱更新,無需手動按 ctrl+F9》 文章。看了下評論,貌似其它人也存在失敗的情況。 反正這裡也寫下步驟,胖友可以自己嘗試一波~萬一成功了,請一定留言,我好找找具體原因。

③ 可能有胖友會覺得手動 Build Project 有點麻煩,IDEA 還提供的自動編譯的選項。設置方式,點擊 IDEA 的菜單 IntelliJ IDEA -> Preference...,然後選擇 Compiler 選項卡,將 Build project automatically 勾選上。如下圖所示:

友情提示:注意,Build project automatically 後面的一行提示,自動編譯僅在項目不處於運行,或者處於 Debug 運行中時,才會自動生效。

所以一定要 Debug 運行 Spring Boot 項目。具體的效果,胖友自己重複 ① ② 兩個步驟,自己嘗試下。

另外,網上我們會看到教程,建議將 compiler.automake.allow.when.app.running 勾選上。

  • 原因是,自動編譯在 Running 運行中默認是不生效的,通過勾選上 compiler.automake.allow.when.app.running,允許在 Running 運行中也生效。
  • 個人建議的話,不要勾選。如果 Running 運行中修改了程式碼,也會導致熱部署,不太合適。如果真要熱部署,使用 Debug 運行項目更合理。

2.2 結論

因為 spring-boot-devtools 提供的本質是重啟的方式,所以還是會存在我們在文章開頭所提到的問題。不過不要慌,實際上 IDEA 自帶了熱部署的方式,畢竟是宇宙第一 Java 開發工具,吹爆就完事了。

3. IDEA 熱部署

友情提示:如果胖友看了「2. spring-boot-devtools」小節,並進行了相關操作,請全部復原,特別是去掉 spring-boot-devtools 依賴。

IDEA 提供了 HotSwap 插件,可以實現真正的熱部署。如下圖所示:

3.1 演示

下面,我們來演示下 HotSwap 插件的使用。

① Run 或者 Debug 運行 Spring Boot 應用。

使用瀏覽器,訪問 http://127.0.0.1:8080/demo/echo 介面,返回結果為 "echo"。

② 修改 DemoController 的 #echo() 方法,設置返回值為 "none"。

【關鍵】 我們現在僅僅需要修改了 Java 程式碼,需要重新編譯下程式碼。點擊 IDEA 的菜單 Build -> Build Project,手動進行編譯。如下圖所示:

友情提示:如果胖友嫌棄滑鼠操作太慢,可以使用 Build Project 的快捷鍵:

  • Mac:Command + F9
  • Windows:Ctrl + F9

此時,我們在 IDEA 中可以看修改的類被重載的提示。如下圖所示:

使用瀏覽器,再次訪問 http://127.0.0.1:8080/demo/echo 介面,返回結果為 "none",成功!

③ 嘗試將 Build project automatically 勾選上,希望實現自動編譯,再搭配上 HotSwap 插件的熱部署,豈不是更香?!

結果失敗,和「2.1 演示」出現一樣的問題,略微蛋疼。

這裡我們來換一種方式,也能實現自動編譯。操作步驟如下圖:

  • 要注意,需要焦點從 IDEA 離開。例如說,在我們修改完介面的程式碼之後,可能會切換到瀏覽器或者 Postman 對該介面進行測試,此時 IDEA 就會自動更新程式碼和資源,進行熱部署。

現在,我們來 修改 DemoController 的 #echo() 方法,設置返回值為 "todo"。

切換到瀏覽器再趕緊切換到 IDEA 中,以達到 IDEA 失去焦點的效果。我們在 IDEA 中可以看修改的類被重載的提示。如下圖所示:

使用瀏覽器,再次訪問 http://127.0.0.1:8080/demo/echo 介面,返回結果為 "todo",成功!

3.2 結論

艿艿個人的喜好的話,使用 IDEA 熱部署為主,通過快捷鍵來手動編譯。畢竟,我是一個「主動」的人,默默開一波車,哈哈哈。

當然,不是說自動編譯有什麼不好,只是每個人的選擇。具體的,胖友可以都試試,尋找一個自己喜歡的方式。

4. Jrebel

Jrebel 是比較有名的一款 Java 熱部署插件。

因為 IDEA 自帶的 HotSwap 插件已經能夠滿足我們的熱部署的訴求,所以本文也不多嗶嗶啥了。真的感興趣的胖友,可以看看《IDEA JRebel 插件熱部署(史上最全)》文章。不過,必要性不大,嘿嘿。

Tags: