skip to content
BlogZzz

[Learn] Spring Boot 入門:用 BootController 建立第一個 API

/ 7 min read

Table of Contents

前面的 Java 文章整理了 class、interface 與 constructor,接下來把這些概念放進一個可以接收 HTTP request 的程式

Spring Boot 系列分成三篇,依序閱讀即可在同一個專案上繼續修改

  • 第一篇:用 BootController 建立 API,理解 request 如何進入 Java method
  • 第二篇:用 Service 與 Repository 拆開業務規則和資料存取
  • 第三篇:理解 DI、Bean 與 Spring 如何建立這些物件

這篇的 BootController 是自己取的 class 名稱,不是 Spring 提供的特殊 class,也沒有 @BootController annotation

Spring 與 Spring Boot 各自做什麼

Spring Framework 提供 DI container、Spring MVC、transaction 等能力

Spring Boot 建立在 Spring 之上,提供 starter dependency、auto-configuration 與應用程式啟動方式,減少開始寫功能之前需要處理的設定

以 HTTP API 為例,選入 Spring Web 後,Boot 會依照 dependency 與設定配置 web application 所需的元件,通常也會啟動 embedded server

不是每一個 Spring Boot 專案都一定是 web server,這取決於加入的 dependency 與應用程式設定

建立範例專案

開啟 Spring Initializr,使用以下設定

欄位值
ProjectMaven
LanguageJava
Spring Boot選穩定版,不選 SNAPSHOT 或 milestone
Groupcom.example
Artifactbootdemo
Package namecom.example.bootdemo
PackagingJar
Java21
DependenciesSpring Web

本系列使用 Java 21,範例採用 Spring Boot 3.x/4.x 共通的 Spring MVC 與 DI API,dependency 版本交由 Initializr 產生的專案管理

解壓縮後用 IDE 開啟專案,保留產生的 pom.xml 與 Maven Wrapper,不需要自己下載 Spring JAR

確認 terminal 與 IDE 使用相同的 JDK

Terminal window
java -version
./mvnw -version

Windows 可以把本文的 ./mvnw 改成 mvnw.cmd

這篇會用到以下檔案

bootdemo/
├── pom.xml
├── mvnw
└── src/main/java/com/example/bootdemo/
├── BootdemoApplication.java
├── BootController.java
└── BootMessage.java

Application 是啟動入口

Initializr 會產生 BootdemoApplication.java

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

main 仍然是 Java 的程式入口,SpringApplication.run(...) 則負責啟動 Spring application

@SpringBootApplication 組合了三個主要能力

  • @SpringBootConfiguration:讓這個 class 成為 configuration,底層也是 @Configuration
  • @EnableAutoConfiguration:根據 dependency、設定與既有 Bean 決定套用哪些自動設定
  • @ComponentScan:掃描元件,預設從這個 class 所在的 package 往下找

因此把入口放在 com.example.bootdemo,其他元件放在相同 package 或 subpackage,是容易理解的起點

如果 Controller 放在 com.other,預設掃描不會找到它,即使有加 annotation 也不會自動出現 API

這些能力的組合可對照 Spring Boot 官方說明

BootController 接收 HTTP request

建立 BootController.java

package com.example.bootdemo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/boot")
public class BootController {
@GetMapping("/hello")
public BootMessage hello(
@RequestParam(name = "name", defaultValue = "visitor") String name) {
return new BootMessage("Hello, " + name);
}
}

再建立 BootMessage.java

package com.example.bootdemo;
public record BootMessage(String message) {
}

record 適合用來表達簡單資料,這裡會有 constructor、message() accessor,以及產生的 equals、hashCode、toString

這個 record 只有一個 String component,拿來當 response DTO 很直接,DTO 是用來傳遞資料的物件

幾個 annotation 各自負責不同的事情

annotation作用
@RestController標記 web controller,method 回傳值預設寫入 response body
@RequestMapping("/api/boot")設定這個 class 的共同路徑
@GetMapping("/hello")把 GET request 對應到 method
@RequestParam把 query parameter 綁定到 Java parameter

class 與 method 上的路徑合起來,就是 GET /api/boot/hello

@RequestParam 明確寫出 name,可以直接看出 URL 參數的名稱,也不需要靠 compiler 保留 Java parameter name 才能推導

啟動並觀察 response

在包含 pom.xml 的目錄執行

Terminal window
./mvnw spring-boot:run

保持這個 terminal 執行,再開另一個 terminal 發送 request

Terminal window
curl -i 'http://localhost:8080/api/boot/hello?name=Amy'

預期 HTTP status 是 200,response body 如下

{ "message": "Hello, Amy" }

沒有提供 name 時會套用預設值

Terminal window
curl 'http://localhost:8080/api/boot/hello'
{ "message": "Hello, visitor" }

Java method 回傳的是 BootMessage object,Spring MVC 透過 HTTP message converter 把它轉成 JSON,不需要自己組 JSON 字串

這裡依賴 Spring Web 專案配置的 JSON 支援,@RestController 本身不代表任何型別都一定輸出 JSON,例如回傳 String 通常會得到文字內容

Controller 與 JSON response 的基本行為可對照 Spring 官方 REST guide

一次 request 經過哪些地方

curl / browser
↓ HTTP GET /api/boot/hello?name=Amy
embedded web server
↓
Spring MVC DispatcherServlet
↓ 根據 mapping 找到 handler,綁定 parameter
BootController.hello("Amy")
↓ 回傳 BootMessage
HTTP message converter
↓
HTTP 200 + JSON response body

Controller 的 method 不需要自己讀 socket,也不需要自己解析完整 HTTP request

接下來加入資料查詢時,可以讓 Controller 專心處理 HTTP,再把工作交給其他 Java object

常見混淆

@Controller 和 @RestController 不完全相同

一般 @Controller 的 method 若回傳 String,通常會被當成 view name;要直接寫入 response body,可以另外加 @ResponseBody

@RestController 組合了 @Controller 與 @ResponseBody,很適合這種回傳資料的 API

@GetMapping 也不是「讓 method 自己開始執行」的 annotation,必須有符合路徑與 HTTP method 的 request 才會進入對應處理流程

如果得到 404,先檢查完整 URL、mapping 與 Controller 的 package;如果啟動時顯示 port 已被使用,可以先在 src/main/resources/application.properties 加上以下設定

server.port=8081

改完重啟程式,curl 的 port 也要一起改成 8081

接下來拆出 Service 與 Repository

現在 Controller 直接組合訊息,功能很小時容易閱讀

當需求變成「查詢學生,再判斷是否及格」,就會同時碰到 HTTP、業務規則與資料來源