1. 概述
在本教程中,我們將學習如何在 Spring 中設置 RESTful API,包括控制器和 HTTP 響應代碼、負載映射配置以及內容協商。
2. 依賴項
為了使用 Spring Boot 創建 REST API,我們需要 Spring Boot Starter Web 依賴項,該依賴項包含用於構建 Web 應用程序、處理 HTTP 請求和 JSON 序列化的庫。只需在我們的 pom.xml 中包含以下依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Spring Boot 自動配置 Jackson 作為 Java 對象和 JSON 之間的默認序列化器和反序列器。
3. 控制器
@RestController 是整個 RESTful API 的 Web 層中的核心 Artifact。 對於本文檔的目的,控制器正在建模一個簡單的 REST 資源,Foo:
@RestController
@RequestMapping("/foos")
class FooController {
@Autowired
private IFooService service;
@GetMapping
public List<Foo> findAll() {
return service.findAll();
}
@GetMapping(value = "/{id}")
public Foo findById(@PathVariable("id") Long id) {
return RestPreconditions.checkFound(service.findById(id));
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Long create(@RequestBody Foo resource) {
Preconditions.checkNotNull(resource);
return service.create(resource);
}
@PutMapping(value = "/{id}")
@ResponseStatus(HttpStatus.OK)
public void update(@PathVariable( "id" ) Long id, @RequestBody Foo resource) {
Preconditions.checkNotNull(resource);
RestPreconditions.checkNotNull(service.getById(resource.getId()));
service.update(resource);
}
@DeleteMapping(value = "/{id}")
@ResponseStatus(HttpStatus.OK)
public void delete(@PathVariable("id") Long id) {
service.deleteById(id);
}
}
正如我們所看到的,我們正在使用一種簡單直觀的 Guava 風格的 RestPreconditions 工具:
public class RestPreconditions {
public static <T> T checkFound(T resource) {
if (resource == null) {
throw new MyResourceNotFoundException();
}
return resource;
}
}
控制器的實現是非公共的,因為不需要公開訪問。
通常,控制器是依賴鏈中的最後一個,它接收來自 Spring 前端控制器(DispatcherServlet)的 HTTP 請求,並簡單地將它們委託轉發到服務層。如果控制器沒有必要被注入或通過直接引用進行操作,那麼我們可能更喜歡不將其聲明為公共的。
請求映射很簡單。與任何控制器一樣,映射的實際值以及 HTTP 方法確定請求的目標方法。 @RequestBody 將方法的參數綁定到 HTTP 請求的 body,而 ResponseBody 則將響應和返回類型綁定。
@RestController 是一個簡寫,用於在我們的類中包含 @ResponseBody 和 @Controller 註解。
它們還確保資源將使用正確的 HTTP 轉換器進行序列化和反序列化。內容協商將發生,以選擇將用於其中的一個活動轉換器,該轉換器主要基於 Accept 標頭,儘管其他 HTTP 標頭也可能用於確定表示形式。
4. 測試 Spring 上下文
當測試 Spring Boot 應用程序時,由於 Spring Boot 的自動配置和測試註解,流程會變得更加容易。
如果我們想在不啓動服務器的情況下測試完整的應用程序上下文,可以使用 @SpringBootTest 註解。
有了這個註解,我們還可以添加 @AutoConfigureMockMvc 來注入一個 MockMvc 實例併發送 HTTP 請求:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class FooControllerAppIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
public void whenTestApp_thenEmptyResponse() throws Exception {
this.mockMvc.perform(get("/foos")
.andExpect(status().isOk())
.andExpect(...);
}
}
為了只測試 Web 層並避免加載應用程序中的不必要部分,可以使用 @WebMvcTest 註解:
@RunWith(SpringRunner.class)
@WebMvcTest(FooController.class)
public class FooControllerWebLayerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private IFooService service;
@Test()
public void whenTestMvcController_thenRetrieveExpectedResult() throws Exception {
// ...
this.mockMvc.perform(get("/foos")
.andExpect(...);
}
}
通過使用 @WebMvcTest,我們僅關注測試 MVC 層,Spring Boot 會自動設置僅控制器層和依賴項(如 Mock 服務)的上下文。
5. HTTP 響應代碼映射
HTTP 響應狀態碼是 RESTful 服務中最重要的部分之一,並且這個主題可能很快變得非常複雜。正確設置這些狀態碼可以決定服務是否成功。
5.1. 未映射的請求
當 Spring MVC 收到沒有映射的請求時,它會認為該請求被拒絕,並返回 405 METHOD NOT ALLOWED 錯誤碼給客户端。
為了更好地實踐,在返回 405 錯誤碼時,應該包含 Allow HTTP 頭部,以指定允許的操作。 這種行為是 Spring MVC 的標準行為,不需要任何額外的配置。
5.2. 有效映射的請求
對於確實具有映射的請求,Spring MVC 會認為該請求有效,並返回 200 OK 響應,除非另有指定其他狀態碼。
因此,控制器聲明瞭不同的 @ResponseStatus 屬性,用於 create、update 和 delete 操作,但沒有為 get 操作指定狀態碼,因為 get 操作應該返回默認的 200 OK。
5.3. 客户端錯誤
在客户端錯誤的情況下,自定義異常會被定義並映射到相應的錯誤碼。
從 Web 層中的任何位置拋出這些異常,將確保 Spring 映射到 HTTP 響應的相應狀態碼:
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
//
}
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
//
}
這些異常是 REST API 的一部分,因此我們應該只在與 REST 相關的適當層中使用它們;例如,如果存在 DAO/DAL 層,則不應直接使用這些異常。
此外,這些異常不是檢查異常,而是與 Spring 實踐和習慣相符的運行時異常。
5.4. 使用 @ExceptionHandler
另一種將自定義異常映射到特定狀態碼的方法是使用控制器中的 @ExceptionHandler 註解。 但是,這種方法的問題在於,該註解僅適用於其定義的控制器。 這意味着我們需要在每個控制器中單獨聲明它們。
當然,Spring 和 Spring Boot 提供了更多處理錯誤的方法,這些方法提供了更大的靈活性。
6. 結論
本文介紹瞭如何使用 Spring 和 Java 配置實現和配置 REST 服務。
在後續的文章中,我們將重點關注 API 的可發現性、高級內容協商以及與 資源的其他表示形式一起工作。