在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的依賴
<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配置
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進行熱部署重啓。
在自動重啓中有幾點需要注意:
- 自動重啓會記錄日誌的
(記錄在什麼情況下重啓的日誌)
可以通過如下關閉
spring:
devtools:
restart:
log-condition-evaluation-delta: false
- 排除一些不需要自動重啓的資源
某些資源在更改時不一定需要觸發重新啓動。默認情況下,改變資源/META-INF/maven,/META-INF/resources,/resources,/static,/public,或/templates不觸發重新啓動,但確會觸發現場重裝。如果要自定義這些排除項,可以使用該spring.devtools.restart.exclude屬性。例如,要僅排除/static,/public你將設置以下屬性:
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”類加載器的項目。該屬性的值是應用於類路徑的正則表達式模式,如以下示例所示:
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 的名爲的項目,執行以下操作:
- 選擇Run Configurations…從Run菜單。
- 創建一個新的Java Application“啓動配置”。
- 瀏覽my-app項目。
- 使用org.springframework.boot.devtools.RemoteSpringApplication作爲主類。
- 添加https://myapp.cfapps.io到Program arguments(或任何你的遠程 URL)。
正在運行的遠程客戶端可能類似於以下列表:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ ___ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | | _ \___ _ __ ___| |_ ___ \ \ \ \
\\/ ___)| |_)| | | | | || (_| []::::::[] / -_) ' \/ _ \ _/ -_) ) ) ) )
' |____| .__|_| |_|_| |_\__, | |_|_\___|_|_|_\___/\__\___|/ / / /
=========|_|==============|___/===================================/_/_/_/
:: 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 'https://'.
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類找到對應的默認配置。
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文件中:
spring:
devtools:
restart:
trigger-file: ".reloadtrigger"
如果我不用devtool,還有什麼選擇?
如果我不用devtool,還有什麼選擇?
在實際的開發過程中,我也不會去使用devtool工具, 因爲:
- devtool本身基於重啓方式,這種仍然不是真正的熱替換方案,JRebel纔是(它是收費的)
- 開發調試最重要的還是一種權衡
- 自動重啓的開銷如果和手動重啓沒有什麼太大差別,那麼還不如手動重啓(按需重啓)
- 多數情況下,如果是方法內部的修改或者靜態資源的修改,在IDEA中是可以通過Rebuild(Ctrl + Shift + F9)進行熱更的
- 此外還有一個工具spring loaded, 可實現修改類文件的熱部署,具體可看其github地址上的說明。
示例源碼
https://github.com/realpdai/tech-pdai-spring-demos
參考文章
https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.devtools