1. 引言
在本教程中,我們將探討 Spring MVC 的一項新特性,該特性允許我們使用標準的 Java 接口來指定 Web 請求。
2. 概述
通常,在 Spring MVC 中定義控制器時,我們會使用各種註解來裝飾其方法,這些註解指定了請求:端點的 URL、HTTP 請求方法、路徑變量等等。
例如,我們可以使用上述註解來裝飾一個普通的函數,引入 /save/{id} 端點。
@PostMapping("/save/{id}")
@ResponseBody
public Book save(@RequestBody Book book, @PathVariable int id) {
// implementation
}自然地講,當只有一個控制器處理請求時,這沒有任何問題。但當有多個控制器具有相同的類方法簽名時,情況就會發生變化。
例如,我們可能有兩個不同版本的控制器——由於遷移或其他原因——它們具有相同的類方法簽名。在這種情況下,我們將會擁有大量的與類方法定義相關的重複註解。這顯然會違反 DRY (不要重複自己) 原則。
如果這種情況發生在純 Java 類上,我們只需定義一個接口並使類實現該接口。在控制器中,類方法的負擔主要不是由於類方法簽名,而是由於類方法註解。
控制器參數註解也會在接口上被檢測到:從而實現控制器接口中的完整映射合同。
讓我們研究一下如何利用這個特性。
3. 控制器接口
3.1. 環境搭建
我們通過一個非常簡單的 REST 應用來演示新功能,該應用管理圖書。該應用將只包含一個控制器,其中包含允許我們檢索和修改圖書的方法。
在教程中,我們僅關注與該功能相關的各個方面。應用程序的全部實現細節可以在我們的 GitHub 倉庫 中找到。
3.2. 接口
以下定義了一個典型的 Java 接口,其中不僅定義了方法的簽名,還指定了它們所處理的 Web 請求類型:
@RequestMapping("/default")
public interface BookOperations {
@GetMapping("/")
List<Book> getAll();
@GetMapping("/{id}")
Optional<Book> getById(@PathVariable int id);
@PostMapping("/save/{id}")
public void save(@RequestBody Book book, @PathVariable int id);
}請注意,我們可能同時擁有類級別的註解以及方法級別的註解。現在,我們可以創建一個實現該接口的控制器:
@RestController
@RequestMapping("/book")
public class BookController implements BookOperations {
@Override
public List<Book> getAll() {...}
@Override
public Optional<Book> getById(int id) {...}
@Override
public void save(Book book, int id) {...}
}我們仍然應該為我們的控制器添加 @RestController 或 @Controller 類級別註解。 這樣定義後,控制器將繼承所有與映射 Web 請求相關的註解。
為了確認控制器現在按預期工作,讓我們運行應用程序並通過發出相應的請求來調用 getAll() 方法:
curl http://localhost:8081/book/儘管控制器實現了接口,我們還可以通過添加 Web 請求註解對其進行進一步微調。我們可以按照定義接口時的方式進行,即在類級別或方法級別。事實上,我們在定義控制器時已經使用了這種可能性:
@RequestMapping("/book")
public class BookController implements BookOperations {...}如果我們在控制器中添加 Web 請求註解,它們將覆蓋接口中的註解。換句話説,Spring 將控制器接口的解釋方式類似於 Java 處理繼承的方式。
我們定義所有通用的 Web 請求屬性在接口中,但在控制器中,我們總是可以對其進行微調。
3.3. 警告提示
當我們擁有一個接口以及實現它的各種控制器時,可能會出現一個請求被多個方法處理的情況。 這樣 Spring 框架會自動拋出異常。
Caused by: java.lang.IllegalStateException: Ambiguous mapping.如果我們在控制器上使用 @RequestMapping 註解,可以降低歧義映射的風險。
4. 結論
在本教程中,我們探討了 Spring 5.1 中引入的新特性。現在,當 Spring MVC 控制器實現一個接口時,它們不僅以標準 Java 的方式進行,還繼承了接口中定義的所有與 Web 請求相關的功能。