The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

使用 Spring Boot CLI 快速開發 Spring Boot 應用程式

昨天介紹了 SDKMAN 這套工具,文章中有安裝 Spring Boot CLI 這套命令列工具,雖然在有 IDE 的情形下已經用不太到這個 CLI 工具,不過我還是大致看了一下,結果發現用 Groovy 開發 Spring Boot 還蠻棒的!👍

Spring Boot

簡介 Spring Boot CLI

Spring Boot CLI 是一個命令列工具 (CLI Tool),如果你想快速開發 Spring 應用程時的時候,是可以用這套工具輔助你快速完成一個應用程式開發。你只要先撰寫一份 Groovy 腳本(scripts),就可以立即透過 Spring Boot CLI 執行出一個 Spring Boot 應用程式,完全不用任何前置作業。

當然,你也可以使用 Spring Boot CLI 來快速建立一個基於 MavenGradle 的 Spring Boot 專案 (透過 Spring Initializr 服務)。

Spring Boot CLI 基本使用方式

在你透過 SDKMAN 執行 sdk install springboot 命令安裝成功後,直接執行 springspring help 命令,就可以得到基本使用方式說明。

usage: spring [--help] [--version]
      <command> [<args>]

Available commands are:

  run [options] <files> [--] [args]
    Run a spring groovy script

  grab
    Download a spring groovy script's dependencies to ./repository

  jar [options] <jar-name> <files>
    Create a self-contained executable jar file from a Spring Groovy script

  war [options] <war-name> <files>
    Create a self-contained executable war file from a Spring Groovy script

  install [options] <coordinates>
    Install dependencies to the lib/ext directory

  uninstall [options] <coordinates>
    Uninstall dependencies from the lib/ext directory

  init [options] [location]
    Initialize a new project using Spring Initializr (start.spring.io)

  encodepassword [options] <password to encode>
    Encode a password for use with Spring Security

  shell
    Start a nested shell

Common options:

  --debug Verbose mode
    Print additional status information for the command you are running


See 'spring help <command>' for more information on a specific command.

從說明中可以發現,裡面有許多子命令都是透過 groovy script 來執行的,因此你可以將其視為 Groovy Script 的啟動器,只是他主要用來跑 Spring Boot 應用程式而已。

  • 查看初始化 Spring Boot 命令的可用選項

    spring init --list
    

    這裡將會顯示所有 spring init 可用餐數的清單與預設值:

    Parameters
    +-------------+------------------------------------------+------------------------------+
    | Id          | Description                              | Default value                |
    +-------------+------------------------------------------+------------------------------+
    | artifactId  | project coordinates (infer archive name) | demo                         |
    | bootVersion | spring boot version                      | 2.7.3                        |
    | description | project description                      | Demo project for Spring Boot |
    | groupId     | project coordinates                      | com.example                  |
    | javaVersion | language level                           | 17                           |
    | language    | programming language                     | java                         |
    | name        | project name (infer application name)    | demo                         |
    | packageName | root package                             | com.example.demo             |
    | packaging   | project packaging                        | jar                          |
    | type        | project type                             | maven-project                |
    | version     | project version                          | 0.0.1-SNAPSHOT               |
    +-------------+------------------------------------------+------------------------------+
    
  • 建立 Spring Boot 應用程式專案目錄

    spring init --dependencies=web,lombok my-project
    

    此命令執行時訊息如下,他其實還是透過 https://start.spring.io 網站來產生一個專案範本,並且上述命令會自動產生一個 my-project 資料夾:

    Using service at https://start.spring.io
    Project extracted to '/home/will/projects/my-project'
    
  • 建立 Spring Boot 應用程式專案壓縮檔

    如果想產生一個專案範本的壓縮檔 (my-project.zip),可以這樣執行:

    spring init --dependencies=web,lombok my-project.zip
    

    預設執行 spring init 命令 (不加上任何參數),會預設產生一個 demo.zip 檔案。

  • 建立 Spring Boot 應用程式專案目錄,並使用 Gradle Build Tool

    如果想產生一個使用 Gradle、指定 Java 1.8 版本、並以 war 封裝的 Spring Boot 專案,可以這樣執行:

    spring init --build=gradle --java-version=1.8 --dependencies=web,lombok --packaging=war my-project2
    

體驗 Spring Boot CLI 建立專案

Spring Boot CLI 建立出來的 Spring Boot 專案,都已經套用了

  1. 建立 Spring Boot 應用程式專案目錄 (demo1)

    spring init --dependencies=web,lombok --groupId=com.duotify demo1
    

    這個 demo1 資料夾將會呈現以下目錄結構:

    $ tree
    .
    ├── HELP.md
    ├── mvnw
    ├── mvnw.cmd
    ├── pom.xml
    └── src
        ├── main
        │   ├── java
        │   │   └── com
        │   │       └── duotify
        │   │           └── demo2
        │   │               └── DemoApplication.java
        │   └── resources
        │       ├── application.properties
        │       ├── static
        │       └── templates
        └── test
            └── java
                └── com
                    └── duotify
                        └── demo2
                            └── DemoApplicationTests.java
    
    14 directories, 7 files
    
  2. 使用 Visual Studio Code 開啟 demo1 資料夾

    code demo1
    

    若是第一次開啟 VS Code 請安裝 Essential Java Spring Boot Snippets 擴充套件。

  3. 加入預設首頁

    先找到應用程式入口點,也就是有標示 @SpringBootApplication 的類別

    src/main/java/com/duotify/demo1/DemoApplication.java
    

    原始碼長這樣:

    package com.duotify.demo1;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class DemoApplication {
    
      public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
      }
    
    }
    

    這裡的 @SpringBootApplication 是一個集合標注,他也同時意味著標注了 @Configuration@EnableAutoConfiguration@ComponentScan 這三個 Annotation,因此 DemoApplication 本身就是個自動設定檔。

    只要在 DemoApplication 類別再標示個 @RestController 並加入一個 home() 方法,標示 @GetMapping("/") 就有了預設首頁! 👍

    package com.duotify.demo1;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @SpringBootApplication
    @RestController
    public class DemoApplication {
    
      @GetMapping("/")
      public String home() {
        return "Hello World";
      }
    
      public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
      }
    
    }
    
  • 啟動專案

    在 VS Code 裡面可以直接按下 F5 功能鍵,快速啟動專案。

    如果要在 Terminal 執行可輸入以下命令:

    mvn spring-boot:run
    

    注意: 你不用事先安裝 spring-boot 這個 Maven Plugin 就可以執行該命令,他會自動下載 spring-boot plugin 並執行裡面的 run 目標(Goal)。

    透過瀏覽器開啟 http://localhost:8080/ 即可看到網站!

    http://localhost:8080/

體驗 Spring Boot CLI 執行 Groovy script

  1. 建立一個 hello.groovy 腳本

    以下命令跑在 Bash 環境下:

    cat <<EOF > hello.groovy
    @RestController
    class WebApplication {
    
        @RequestMapping("/")
        String home() {
            "Hello World!"
        }
    
    }
    EOF
    
  2. 直接執行 hello.groovy 腳本

    spring run hello.groovy
    

    spring run hello.groovy

    透過瀏覽器開啟 http://localhost:8080/ 即可看到網站!

    http://localhost:8080/

    事實上,你還可以對這個應用程式做出微調,例如你不想將應用程式跑在 Port 8080,改用 Port 9000 的命令如下:

    spring run hello.groovy -- --server.port=9000
    

    記得中間要用 -- 隔開參數。

    如果你想傳入額外的 JVM 參數,可以透過 JAVA_OPTS 環境變數來傳入,例如:

    JAVA_OPTS=-Xmx1024m spring run hello.groovy
    

    注意: 這裡的 JAVA_OPTS=-Xmx1024m 環境變數設定只會套用在這一次的命令列執行而已。

仔細想想,這也太酷了吧,整個 Spring Boot 網站,就只要一個檔案就可以跑起來了耶!👍

如果你有超過不止一個 API 要執行,你可以撰寫多個 *.groovy 檔案,例如:

  1. 建立一個 test.groovy 腳本

    以下命令跑在 Bash 環境下:

    cat <<EOF > test.groovy
    @RestController
    class TestController {
    
        @RequestMapping("/test")
        String home() {
            "Test!"
        }
    
    }
    EOF
    
  2. 直接透過 shell globbing 語法 (*.groovy) 執行所有腳本

    spring run *.groovy
    

    透過瀏覽器開啟 http://localhost:8080/http://localhost:8080/test 都可看到網站!

帥呀老皮!有夠乾淨的專案架構!😍

那我們如果要封裝應用程式為 JAR 檔可以嗎?可以的!

  1. *.groovy 封裝成 demo2.jar

    spring jar demo2.jar *.groovy
    

    image

    這裡的 demo2.jar.original 是你的原始應用程式內容,而 demo2.jar 則是包含 Tomcat 的內容,所以檔案大很多。

    請注意: 使用 spring jar 封裝的時候,預設會包含 public/**, resources/**, static/**, templates/**, META-INF/**, * 這些檔案與路徑,且預設會排除 .*, repository/**, build/**, target/**, **/*.jar, **/*.groovy 這些檔案與路徑。你也可以透過 --include--exclude 自行加入或排除更多檔案到 JAR 檔中。

  2. 執行 JAR 檔

    java -jar demo2.jar
    

Groovy 是如何使用第三方套件的?

雖然 Groovy 是一個基於 Java 的動態腳本語言,背後其實還是跑在 JVM 底下,那 Java 原本需要的那些 Code 怎麼可能憑空消失呢?說好的 import 呢?說好的 classPath 呢?還有 Spring Boot 需要的那些相依套件 (例如 spring-boot-starter-web, spring-boot-starter-data-jpa, ... 之類的) 又到哪裡去了?

在標準的 Groovy 語言中有個 @Grab 標注(annotation),可以讓你宣告第三方函式庫的相依性,因此可以讓 Groovy 像 Maven 或 Gradle 那樣自動下載 JARs 檔,但卻不需要任何建置工具。

而 Spring Boot 則是利用了這個技術,直接依據你所寫的程式碼自動抓取(Grab)你需要的函式庫或套件。例如我們剛剛的 hello.groovy 腳本,在 WebApplication 類別上用到了 @RestController 標注,因此會自動抓取 TomcatSpring MVC 套件回來,是不是真的很酷!😍

cat <<EOF > hello.groovy
@RestController
class WebApplication {

    @RequestMapping("/")
    String home() {
        "Hello World!"
    }

}
EOF

官網的 Spring Boot CLI 文件有列出一些對應表,讓你知道寫了哪些程式,就會自動下載哪些套件:

Items Grabs
JdbcTemplate, NamedParameterJdbcTemplate, DataSource JDBC Application.
@EnableJms JMS Application.
@EnableCaching Caching abstraction.
@Test JUnit.
@EnableRabbit RabbitMQ.
extends Specification Spock test.
@EnableBatchProcessing Spring Batch.
@MessageEndpoint @EnableIntegration Spring Integration.
@Controller @RestController @EnableWebMvc Spring MVC + Embedded Tomcat.
@EnableWebSecurity Spring Security.
@EnableTransactionManagement Spring Transaction Management.

你只要去 Spring Boot CLI 的原始碼專案找出所有 CompilerAutoConfiguration 的子類別,就可以知道怎麼運作的了。

除了這些內建會自動抓取的相依套件外,你在 Spring Boot 可以透過 @Grab('freemarker') 語法,宣告需要使用到哪些套件,無須指定 groupversion 等資訊,完整的短名清單可以參閱 Dependency Coordinates 文件。

你可以透過 spring grab *.groovy 命令,自動將所有程式碼中用到的相依套件全部下載到 ./repository 目錄下。

在用 Groovy 撰寫 Spring Boot 的時候,大多數情況都是不用特別撰寫 import 語法的,例如 @Component, @RestController@RequestMapping 之類的標注通通不需要特別明確匯入,因此程式碼十分乾淨清爽!👍

最後,使用 Groovy 撰寫 Spring Boot 時,可以不用特別寫啟動類別與 public static void main(String[] args) 方法,連 SpringApplication 實體都不用建立,Spring Boot CLI 會自動幫你與原始碼一起建立,是不是很棒!👍

總結

我之前沒想過要用 Apache Groovy 來寫 Spring Boot 應用程式,而且我也很少聽到有人這麼做,但研究過 Spring Boot CLI 之後,我覺得用 Groovy 也蠻好的耶,整個目錄架構與程式碼都非常簡潔,而且底層一樣是走 JVM 架構,與現有的任何 Java 相依套件都相容,也沒有什麼效能問題,自動化部署與封裝也都很正常。

Apache Groovy 畢竟是一個新的程式語言,有他自己的語言特性,語法也跟 Java 有些許差異,雖然學習門檻沒有很高,但不一樣就是不一樣,要推廣應該不太容易吧。

相關連結

留言評論