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 層級中的核心組件。 鑑於本文檔的目的,控制器模擬一個簡單的 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 則將響應和返回類型綁定到 body。
@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 狀態碼,除非另有指定。
這是因為 Spring MVC 這樣做,因此控制器聲明瞭不同的 <em @ResponseStatus</em>> 用於 <em create</em>>, <em update</em>>, 和 <em delete</em>> 操作,但未對 <em get</em>> 操作進行指定,該操作確實應該返回默認的 200 OK 狀態碼。
5.3. 客户端錯誤
當發生客户端錯誤時,自定義異常將被定義並映射到適當的錯誤代碼。
僅僅從 Web 層次結構的任何層拋出這些異常將確保 Spring 將相應的 HTTP 響應狀態碼映射到 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 的可發現性、高級內容協商以及與 資源 的其他表示形式一起工作。