Halo 是一款現代化的個人獨立部落格系統,給習慣寫部落格的同學一個更好的選擇。據說這是一個較容易讀懂的 Spring-Boot 專案,那我就希望通過這個專案學習前輩的經驗。
如有幫助,不勝榮幸。如有錯誤,歡迎指正!
最早看到這個部落格的原始碼的時候是通過 B 站 up 主 -CodeSheep 的一個視訊:Java 企業級開源專案推薦,幫助大家從學習走向實踐,奈何當時自己知識有限,沒有仔細的閱讀原始碼。近日 Halo 也推出了正式版,我也就抱著學習的心態拜讀一下。
首先開啟工程,看到整個工程有以下兩個明顯的變化:
- 配置檔案由 properties 變為 yaml
可以明顯的看到,在處理層級關係的時候,properties 需要使用大量的路徑來描述層級(或者屬性),比如 environments.dev.url 和 environments.dev.name。其次,對於較為複雜的結構,比如陣列(my.servers),寫起來更為複雜。而對應的 YAML 格式檔案就簡單很多:
- 構建工具由 meavn 變為 gradle
因為此前有過 Android 的開發經驗,所以這一改變對我的影響並不大。gradle 逐漸替代 meavn 應該是目前的趨勢,但是目前大部分教學和企業採用的還是以 meavn 為主,所以此前我也未曾嘗試過採用 gradle 構建專案。由此看出 Halo 還是很 Fashion 的。
首先開啟 Application.java
檔案,看看有什麼學習的地方。
@SpringBootApplication
@EnableJpaAuditing
@EnableScheduling
@EnableAsync
@EnableJpaRepositories(basePackages = "run.halo.app.repository", repositoryBaseClass = BaseRepositoryImpl.class)
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
// Customize the spring config location
System.setProperty("spring.config.additional-location", "file:${user.home}/.halo/,file:${user.home}/halo-dev/");
// Run application
SpringApplication.run(Application.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
System.setProperty("spring.config.additional-location", "file:${user.home}/.halo/,file:${user.home}/halo-dev/");
return application.sources(Application.class);
}
}
發現與我之前 Spring-Boot 開發兩點不一樣的地方:
其一:繼承 SpringBootServletInitializer 從而實現 configure 方法
可能找答案的姿勢不對,只零星找到下面幾點描述:
1、啟動類繼承 SpringBootServletInitializer 類,重寫 configure (SpringApplicationBuilder builder) 方法
2、因為想要用 web 容器啟動專案
3、使用外部 servlet 容器
結論:為了 Undertow 容器!
原因:
1、排除內建的 tomcat 容器 - build.gradle
exclude module: 'spring-boot-starter-tomcat
2、新增 Undertow 依賴 - build.gradle
implementation 'org.springframework.boot:spring-boot-starter-undertow
3、配置伺服器
server:
port: 8090
use-forward-headers: true
undertow:
io-threads: 2
worker-threads: 36
buffer-size: 1024
directBuffers: true
結合此前在別人基礎上修改的網盤專案得到了相同的印證。
其二:System.setProperty ("spring.config.additional-location", "...");
開發者給了以下注釋:Customize the spring config location
(定製 spring 配置檔案的位置)
存在以下三個疑問:
1、System.setProperty 有何用?
setProperty (String prop, String value); 1、 設定指定鍵對值的系統屬性,其中 prop:系統屬性的名稱,value:系統屬性的值。注:這裡的 system,系統指的是 JRE (runtime) system,不是指 OS。 2、System.setProperty 相當於一個靜態變數,存在記憶體裡面,可以在專案的任何一個地方,通過 System.getProperty ("變數") 來獲得
2、為何要設定這個變數?
載入外部配置檔案。打包 jar 執行也不方便修改 jar 內部資料,通過設定環境變數 spring.config.location。因為部落格中也有很多配置選項,所以猜想需要從外部讀取某些會改變的配置,需要繼續閱讀原始碼驗證猜想。
3、${user.home} 從何而來?
如從前所示 user.home 應該是一個靜態變數,嘗試列印:
System.out.println(System.getProperty("user.home"));
//列印出:C:\Users\74472 驗證猜想成功
變數 | 含義 |
---|---|
java.version | Java 執行時環境版本 |
java.vendor | Java 執行時環境供應商 |
java.vendor.url | Java 供應商的 URL |
java.home | Java 安裝目錄 |
java.vm.specification.version | Java 虛擬機器規範版本 |
java.vm.specification.vendor | Java 虛擬機器規範供應商 |
java.vm.specification.name | Java 虛擬機器規範名稱 |
java.vm.version | Java 虛擬機器實現版本 |
java.vm.vendor | Java 虛擬機器實現供應商 |
java.vm.name | Java 虛擬機器實現名稱 |
java.specification.version | Java 執行時環境規範版本 |
java.specification.vendor | Java 執行時環境規範供應商 |
java.specification.name | Java 執行時環境規範名稱 |
java.class.version | Java 類格式版本號 |
java.class.path | Java 類路徑 |
java.library.path | 載入庫時搜尋的路徑列表 |
java.io.tmpdir | 預設的臨時檔案路徑 |
java.compiler | 要使用的 JIT 編譯器的名稱 |
java.ext.dirs | 一個或多個擴充套件目錄的路徑 |
os.name | 作業系統的名稱 |
os.arch | 作業系統的架構 |
os.version | 作業系統的版本 |
file.separator | 檔案分隔符(在 UNIX 系統中是 “/”) |
path.separator | 路徑分隔符(在 UNIX 系統中是 “:”) |
line.separator | 行分隔符(在 UNIX 系統中是 “/n”) |
user.name | 使用者的賬戶名稱 |
user.home | 使用者的主目錄 |
user.dir | 使用者的當前工作目錄 |
Spring-Boot 的專案執行起來還是比較簡單的。頁面也很美觀
Spring-boot 的專案通常都是從 Controller 讀起,發現一個共同的特點,就是程式碼都是類似這樣一個結構:
private final PostService postService;
private final OptionService optionService;
... .... // 其他的Service
private final ThemeService themeService;
public ContentIndexController(PostService postService,
OptionService optionService,
ThemeService themeService) {
this.postService = postService;
this.optionService = optionService;
... .... // 其他的Service
this.themeService = themeService;
}
@GetMapping
public String index(Model model) {
return this.index(model, 1, Sort.by(DESC, "topPriority").and(Sort.by(DESC, "createTime")));
}
@GetMapping(value = "page/{page}")
public String index(Model model,
@PathVariable(value = "page") Integer page,
@SortDefault.SortDefaults({
@SortDefault(sort = "topPriority", direction = DESC),
@SortDefault(sort = "createTime", direction = DESC)
}) Sort sort) {
...//省略
}
學習收穫一:使用構造器注入需要用到的 Service
學習收穫二:使用多型處理請求
真正起作用的是後者,前者只是為使用者新增了引數後呼叫後者。
return this.index(model, 1, Sort.by(DESC, "topPriority").and(Sort.by(DESC, "createTime")));
學習收穫三:RESTful 的 api 設計