【菜鳥讀原始碼】halo✍原始碼學習 (一)

Reyunn發表於2019-09-05

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 的專案執行起來還是比較簡單的。頁面也很美觀
Halo部落格

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 設計

相關文章