SpringBoot開發 - 什麼是熱部署和熱載入?devtool的原理是什麼?

語言: CN / TW / HK

在SpringBoot開發除錯中,如果我每行程式碼的修改都需要重啟啟動再除錯,可能比較費時間;SpringBoot團隊針對此問題提供了spring-boot-devtools(簡稱devtools)外掛,它試圖提升開發除錯的效率。@pdai

準備知識點

什麼是熱部署和熱載入?

熱部署和熱載入是在應用正在執行的時候,自動更新(重新載入或者替換class等)應用的一種能力。(PS:spring-boot-devtools提供的方案也是要重啟的,只是無需手動重啟能實現自動載入而已。)

嚴格意義上,我們需要區分下熱部署和熱載入, 對於Java專案而言:

  • 熱部署
  • 在伺服器執行時重新部署專案
  • 它是直接重新載入整個應用,這種方式會釋放記憶體,比熱載入更加乾淨徹底,但同時也更費時間。

  • 熱載入

  • 在在執行時重新載入class,從而升級應用。
  • 熱載入的實現原理主要依賴java的類載入機制,在實現方式可以概括為在容器啟動的時候起一條後臺執行緒,定時的檢測類檔案的時間戳變化,如果類的時間戳變掉了,則將類重新載入。
  • 對比反射機制,反射是在執行時獲取類資訊,通過動態的呼叫來改變程式行為; 熱載入則是在執行時通過重新載入改變類資訊,直接改變程式行為。

什麼是LiveLoad?

LiveLoad是提供瀏覽器客戶端自動載入更新的工具,分為LiveLoad伺服器和Liveload瀏覽器外掛兩部分; devtools中已經集成了LiveLoad伺服器,所以如果我們開發的是web應用,並且期望瀏覽器自動重新整理, 這時候可以考慮LiveLoad.

同一時間只能執行一個LiveReload伺服器。 開始應用程式之前,請確保沒有其他LiveReload伺服器正在執行。如果從IDE啟動多個應用程式,則只有第一個應用程式將支援LiveReload。

配置devtools實現熱部署

我們通過如下配置來實現自動重啟方式的熱部署。

POM配置

新增spring-boot-devtools的依賴

xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <!-- 可以防止將devtools依賴傳遞到其他模組中 --> </dependency> </dependencies>

IDEA配置

如果你使用IDEA開發工具,通常有如下兩種方式:

  • 方式一: 無任何配置時,手動觸發重啟更新(Ctrl+F9)

(也可以用mvn compile編譯觸發重啟更新)

  • 方式二: IDEA需開啟執行時編譯,自動重啟更新

設定1

File->Setting->Build,Execution,Deployment->Compile

勾選:Make project automatically

設定2

快捷鍵:ctrl+alt+shift+/

選擇:Registry

勾選:compiler.automake.allow.when.app.running

新版本的IDEA可以在File->setting->Advanced Setttings裡面的第一個設定:

application.yml配置

yml spring: devtools: restart: enabled: true #設定開啟熱部署 additional-paths: src/main/java #重啟目錄 exclude: WEB-INF/** thymeleaf: cache: false #使用Thymeleaf模板引擎,關閉快取

使用LiveLoad

spring-boot-devtools模組包含嵌入式LiveReload伺服器,可以在資源更改時用於觸發瀏覽器重新整理。 LiveReload瀏覽器擴充套件程式支援Chrome,Firefox和Safari,你可以從livereload.com免費下載。

或者從瀏覽器外掛中心下載,比如firefox:

安裝完之後,可以通過如下圖示管理

如果你不想在應用程式執行時啟動LiveReload伺服器,則可以將spring.devtools.livereload.enabled屬性設定為false 。

同一時間只能執行一個LiveReload伺服器。 開始應用程式之前,請確保沒有其他LiveReload伺服器正在執行。如果從IDE啟動多個應用程式,則只有第一個應用程式將支援LiveReload。

進一步理解

雖然一些開發者會使用devtool工具,但是很少有能夠深入理解的;讓我們理解如下幾個問題,幫助你進一步理解。@pdai

devtool的原理?為何會自動重啟?

為什麼同樣是重啟應用,為什麼不手動重啟,而是建議使用spring-boot-devtools進行熱部署重啟?

spring-boot-devtools使用了兩個類載入器ClassLoader,一個ClassLoader載入不會發生更改的類(第三方jar包),另一個ClassLoader(restart ClassLoader)載入會更改的類(自定義的類)。

後臺啟動一個檔案監聽執行緒(File Watcher)監測的目錄中的檔案發生變動時, 原來的restart ClassLoader被丟棄,將會重新載入新的restart ClassLoader

因為檔案變動後,第三方jar包不再重新載入,只加載自定義的類,載入的類比較少,所以重啟比較快。

這也是為什麼,同樣是重啟應用,為什麼不手動重啟,建議使用spring-boot-devtools進行熱部署重啟。

在自動重啟中有幾點需要注意:

  • 自動重啟會記錄日誌的

(記錄在什麼情況下重啟的日誌)

可以通過如下關閉

yml spring: devtools: restart: log-condition-evaluation-delta: false

  • 排除一些不需要自動重啟的資源

某些資源在更改時不一定需要觸發重新啟動。預設情況下,改變資源/META-INF/maven,/META-INF/resources,/resources,/static,/public,或/templates不觸發重新啟動,但確會觸發現場重灌。如果要自定義這些排除項,可以使用該spring.devtools.restart.exclude屬性。例如,要僅排除/static,/public你將設定以下屬性:

yml spring: devtools: restart: exclude: "static/**,public/**"

如果要保留這些預設值並新增其他排除項,請改用該spring.devtools.restart.additional-exclude屬性。

  • 自定義重啟類載入器

重啟功能是通過使用兩個類載入器來實現的。對於大多數應用程式,這種方法效果很好。但是,它有時會導致類載入問題。

預設情況下,IDE 中的任何開啟專案都使用“重啟”類載入器載入,任何常規.jar檔案都使用“基本”類載入器載入。如果你處理一個多模組專案,並且不是每個模組都匯入到你的 IDE 中,你可能需要自定義一些東西。為此,你可以建立一個META-INF/spring-devtools.properties檔案。

該spring-devtools.properties檔案可以包含以restart.exclude和為字首的屬性restart.include。該include元素是應該被拉高到“重啟”的類載入器的專案,以及exclude要素是應該向下推入“Base”類載入器的專案。該屬性的值是應用於類路徑的正則表示式模式,如以下示例所示:

yml restart: exclude: companycommonlibs: "/mycorp-common-[\\w\\d-\\.]+\\.jar" include: projectcommon: "/mycorp-myproj-[\\w\\d-\\.]+\\.jar"

更多相關的資訊可以在這裡檢視。

devtool是否會被打包進Jar?

devtool原則上來說應該是隻在開發除錯的時候使用,而在生產環境執行jar包時是不需要的,所以Spring打包會不會把它打進JAR嗎?

  • 預設情況下,不會被打包進JAR

執行打包的應用程式時,開發人員工具會自動禁用。如果你通過 java -jar或者其他特殊的類載入器進行啟動時,都會被認為是“生產環境的應用”。

  • 如果我們期望遠端除錯應用

生產環境勿用,只有在受信任的網路上執行或使用 SSL 進行保護時,才應啟用它

在這種情況下,devtool也具備遠端除錯的能力:遠端客戶端應用程式旨在從你的 IDE 中執行。你需要org.springframework.boot.devtools.RemoteSpringApplication使用與你連線的遠端專案相同的類路徑執行。應用程式的唯一必需引數是它連線到的遠端 URL。

例如,如果使用 Eclipse 或 Spring Tools,並且你有一個my-app已部署到 Cloud Foundry 的名為的專案,執行以下操作:

  1. 選擇Run Configurations…​從Run選單。
  2. 建立一個新的Java Application“啟動配置”。
  3. 瀏覽my-app專案。
  4. 使用org.springframework.boot.devtools.RemoteSpringApplication作為主類。
  5. 新增http://myapp.cfapps.io到Program arguments(或任何你的遠端 URL)。

正在執行的遠端客戶端可能類似於以下列表:

``java . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ ___ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _ | | _ _ _ __ ___| | \ \ \ \ \/ )| |)| | | | | || (| []::::::[] / -) ' \/ _ \ / -) ) ) ) ) ' || .__|| ||| |_, | ||_|||__/____|/ / / / =========||==============|/===================================///_/ :: Spring Boot Remote :: 2.5.4

2015-06-10 18:25:06.632 INFO 14938 --- [ main] o.s.b.devtools.RemoteSpringApplication : Starting RemoteSpringApplication on pwmbp with PID 14938 (/Users/pwebb/projects/spring-boot/code/spring-boot-project/spring-boot-devtools/target/classes started by pwebb in /Users/pwebb/projects/spring-boot/code) 2015-06-10 18:25:06.671 INFO 14938 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2a17b7b6: startup date [Wed Jun 10 18:25:06 PDT 2015]; root of context hierarchy 2015-06-10 18:25:07.043 WARN 14938 --- [ main] o.s.b.d.r.c.RemoteClientConfiguration : The connection to http://localhost:8080 is insecure. You should use a URL starting with 'http://'. 2015-06-10 18:25:07.074 INFO 14938 --- [ main] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729 2015-06-10 18:25:07.130 INFO 14938 --- [ main] o.s.b.devtools.RemoteSpringApplication : Started RemoteSpringApplication in 0.74 seconds (JVM running for 1.105) ```

devtool為何會預設禁用快取選項?

Spring Boot 支援的一些庫使用快取來提高效能。例如,模板引擎快取已編譯的模板以避免重複解析模板檔案。此外,Spring MVC 可以在提供靜態資源時向響應新增 HTTP 快取標頭。

雖然快取在生產中非常有益,但在開發過程中可能會適得其反,使你無法看到剛剛在應用程式中所做的更改。出於這個原因, spring-boot-devtools 預設禁用快取選項。

比如Thymeleaf 提供了spring.thymeleaf.cache來設定模板引擎的快取,使用spring-boot-devtools模組時是不需要手動設定這些屬性的,因為spring-boot-devtools會自動進行設定。

那麼會自動設定哪些配置呢?你可以在DevToolsPropertyDefaultsPostProcessor類找到對應的預設配置。

```java public class DevToolsPropertyDefaultsPostProcessor implements EnvironmentPostProcessor {

static {
    Map<String, Object> properties = new HashMap<>();
    properties.put("spring.thymeleaf.cache", "false");
    properties.put("spring.freemarker.cache", "false");
    properties.put("spring.groovy.template.cache", "false");
    properties.put("spring.mustache.cache", "false");
    properties.put("server.servlet.session.persistent", "true");
    properties.put("spring.h2.console.enabled", "true");
    properties.put("spring.web.resources.cache.period", "0");
    properties.put("spring.web.resources.chain.cache", "false");
    properties.put("spring.template.provider.cache", "false");
    properties.put("spring.mvc.log-resolved-exception", "true");
    properties.put("server.error.include-binding-errors", "ALWAYS");
    properties.put("server.error.include-message", "ALWAYS");
    properties.put("server.error.include-stacktrace", "ALWAYS");
    properties.put("server.servlet.jsp.init-parameters.development", "true");
    properties.put("spring.reactor.debug", "true");
    PROPERTIES = Collections.unmodifiableMap(properties);
}

```

當然如果你不想被應用屬性被spring-boot-devtools預設設定, 可以通過spring.devtools.add-properties到false你application.yml中。

devtool是否可以給所有Springboot應用做全域性的配置?

可以通過將spring-boot-devtools.yml檔案新增到$HOME/.config/spring-boot目錄來配置全域性 devtools 設定

新增到這些檔案的任何屬性都適用於你機器上使用 devtools 的所有Spring Boot 應用程式。例如,要將重新啟動配置為始終使用觸發器檔案,你需要將以下屬性新增到你的spring-boot-devtools檔案中:

yml spring: devtools: restart: trigger-file: ".reloadtrigger"

如果我不用devtool,還有什麼選擇?

如果我不用devtool,還有什麼選擇?

在實際的開發過程中,我也不會去使用devtool工具, 因為: + devtool本身基於重啟方式,這種仍然不是真正的熱替換方案,JRebel才是(它是收費的) + 開發除錯最重要的還是一種權衡 + 自動重啟的開銷如果和手動重啟沒有什麼太大差別,那麼還不如手動重啟(按需重啟) + 多數情況下,如果是方法內部的修改或者靜態資源的修改,在IDEA中是可以通過Rebuild(Ctrl + Shift + F9)進行熱更的

  • 此外還有一個工具spring loaded, 可實現修改類檔案的熱部署,具體可看其github地址上的說明。

示例原始碼

http://github.com/realpdai/tech-pdai-spring-demos

參考文章

http://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.devtools

http://liayun.blog.csdn.net/article/details/116541775

更多內容

告別碎片化學習,無套路一站式體系化學習後端開發: Java 全棧知識體系(http://pdai.tech)