iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
Software Development

從 Laravel 到 Spring Boot:30 天打造縮網址服務系列 第 5

Day5 - Spring MVC Controller/Routing vs Laravel Route/Controller

  • 分享至 

  • xImage
  •  

一個 request 進來後,到底發生了什麼事

面試常被問的經典問題:「使用者打一支 API 進來,中間伺服器做了哪些事?」這其實就是在問完整的請求生命週期。以 Spring Boot 來說,大致是這樣:

Client 發出請求
   ↓
Filter(Servlet 層攔截,例如記錄 log、CORS、驗證 Token)
   ↓
DispatcherServlet(Spring MVC 的總入口,負責分派請求)
   ↓
HandlerMapping(比對 URL,找出該由哪個 Controller 方法處理)
   ↓
Interceptor(Spring 層攔截,例如權限檢查、埋點)
   ↓
Controller(接收請求參數,呼叫 Service)
   ↓
Service(商業邏輯,呼叫 Repository)
   ↓
Repository / JPA(跟資料庫互動)
   ↓
Controller 組裝回應
   ↓
Interceptor → Filter(依序反向執行)
   ↓
回應送回 Client

這篇先聚焦在「入口」——Client 的請求怎麼被對應到某一個 Controller 方法(HandlerMapping 這一段)。中間的 Filter/Interceptor 攔截細節會在 Day 12 展開,Service 層實際的商業邏輯分層則是 Day 09 的主題。把這張全貌圖放在這裡,是希望讀者先有整體地圖,後面兩天再各自深入,不會覺得內容東一塊西一塊。

Routing:Laravel vs Spring Boot

Laravel 的路由是集中定義的,routes/api.php 裡一行對應一個 Controller 方法:

// routes/api.php
Route::get('/links/{code}', [LinkController::class, 'show']);
Route::post('/links', [LinkController::class, 'store']);

Spring Boot 沒有集中的路由檔,路由資訊直接用註解標在 Controller 方法上

@RestController
@RequestMapping("/api/links")
class LinkController {

    @GetMapping("/{code}")
    ResponseEntity<LinkResponse> show(@PathVariable String code) { ... }

    @PostMapping
    ResponseEntity<LinkResponse> store(@RequestBody CreateLinkRequest request) { ... }
}

這是兩邊思維上最大的轉換:Laravel 要理解一支 API,得先去 routes/api.php 查它對應哪個方法;Spring Boot 反過來,路由資訊就寫在方法本身,不用另外查表,但代價是「這個專案總共有哪些 API」沒有一個地方能一次看完,要看 IDE 的 Endpoints 面板或自己搜尋。

路徑參數:Laravel 靠路由裡的 {code} 佔位符,自動綁到方法參數同名的變數;Spring Boot 一樣寫 {code},但方法參數要明確標 @PathVariable 才會知道「這個參數是從路徑抓的」,不是靠參數名稱猜。

Query 參數:Laravel 用 $request->query('foo') 手動從 Request 物件拿;Spring Boot 用 @RequestParam String foo 直接宣告在方法簽章上,等於是把「這支 API 吃哪些 query 參數」寫死在方法介面裡,一看方法簽章就知道,不用再翻方法內部的程式碼。

Route Model Binding:這是 Laravel 特有的語法糖,Spring Boot 沒有直接對應——Laravel 可以直接把路由參數綁定成 Model 實例,框架自動幫你查資料庫、查不到直接回 404:

Route::get('/links/{link}', [LinkController::class, 'show']);

public function show(Link $link) { ... } // $link 已經是查好的 Eloquent Model

Spring Boot 沒有這層自動化,@PathVariable 拿到的永遠只是原始的字串/數字,查資料庫這件事要自己在方法內呼叫 Repository(Day07 開始會接上)。這不是 Spring Boot 少做了什麼,是兩邊對「Controller 該做多少事」的設計哲學不同:Laravel 傾向讓 Controller 更「薰陶」、少寫幾行;Spring Boot 傾向讓每一步都寫明確,換取更好追蹤。

縮網址服務的第一支 API

先只把 Controller 層搭起來,Service/Repository 還沒接(Day07 開始才會真的碰資料庫),這裡的重點是驗證前面講的 routing 機制真的能收到請求、回應:

record CreateLinkRequest(String url) {}
record LinkResponse(String code, String shortUrl) {}

@RestController
@RequestMapping("/api/links")
class LinkController {

    @PostMapping
    ResponseEntity<LinkResponse> store(@RequestBody CreateLinkRequest request) {
        // TODO Day07 開始接上真正的 Service/Repository
        // TODO Day14 才會講怎麼真正產生短碼,這裡先寫死示範
        String code = "abc123";
        return ResponseEntity.ok(new LinkResponse(code, "https://short.ly/" + code));
    }

    @GetMapping("/{code}")
    ResponseEntity<LinkResponse> show(@PathVariable String code) {
        // TODO Day07 開始接上真正的 Service/Repository
        return ResponseEntity.ok(new LinkResponse(code, "https://short.ly/" + code));
    }
}

對照本篇開頭那張生命週期全貌圖,現在打一支 POST /api/links 進來,實際發生的事是:

Client 發出請求
   ↓
Filter          (這篇還沒接,先跳過)
   ↓
DispatcherServlet
   ↓
HandlerMapping   ← 靠 @RequestMapping("/api/links") + @PostMapping,找到 store() 方法
   ↓
Interceptor      (這篇還沒接,先跳過)
   ↓
Controller       ← store() 執行,@RequestBody 把 JSON 轉成 CreateLinkRequest
   ↓
Service/Repository(還沒接,先寫死回傳)
   ↓
Controller 組裝回應(LinkResponse)
   ↓
回應送回 Client

這篇真正走通的只是「入口」那一段——URL 怎麼被對應到方法、參數怎麼被解析。中間打星號跳過的部分,會依序在後面的 Day 補上:Filter/Interceptor 在 Day12,真正的短碼產生邏輯在 Day14,Service/Repository 分層在 Day09、Day07。


上一篇
Day4 - Spring Boot 專案結構與 IoC/DI 容器 vs Laravel Service Container
下一篇
Day6 - 請求驗證 Validation vs Laravel Form Request
系列文
從 Laravel 到 Spring Boot:30 天打造縮網址服務9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言