2. 概述

本教程将指导你在Spring中设置REST API,包括控制器实现、HTTP响应码处理、请求/响应体序列化配置以及内容协商机制。

3. 依赖

使用Spring Boot构建REST API时,只需添加Spring Boot Starter Web依赖即可。它已内置Web开发所需的核心库(HTTP请求处理、JSON序列化等):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Spring Boot会自动将Jackson配置为默认的JSON序列化/反序列化工具。

4. 控制器

@RestController是RESTful API中Web层的核心组件。本文示例实现了一个简单的资源控制器FooController

@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;
    }
}

**控制器类可以不声明为public**——因为它通常位于依赖链末端,只需接收DispatcherServlet转发的请求并委托给服务层。除非需要直接注入控制器,否则没必要用public修饰。

请求映射规则很直观:**@RequestMappingvalue和HTTP方法共同决定目标处理方法**。@RequestBody将HTTP请求体绑定到方法参数,@ResponseBody则将返回对象序列化为响应体。

@RestController相当于@Controller + @ResponseBody的组合注解,确保资源通过合适的HTTP转换器进行序列化/反序列化。内容协商机制会根据Accept等HTTP头自动选择转换器。

5. 测试Spring上下文

Spring Boot的自动配置和测试注解极大简化了测试流程:

全应用上下文测试(不启动服务器):

@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层(避免加载无关组件):

@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时,Spring Boot会自动初始化仅包含Controller层及其依赖(如Mock服务)的精简上下文。

6. 映射HTTP响应码

HTTP响应码是REST服务的核心要素,处理不当可能直接导致服务不可用。

6.1 未映射的请求

当Spring MVC收到无匹配映射的请求时:

  • 返回405 METHOD NOT ALLOWED
  • 自动添加Allow响应头(声明支持的HTTP方法)
  • 这是Spring MVC的默认行为,无需额外配置

6.2 有效的映射请求

所有已映射的请求默认返回200 OK。因此示例控制器中:

  • create/update/delete操作通过@ResponseStatus显式声明状态码
  • get操作保持默认的200 OK

6.3 客户端错误

自定义异常可直接映射为HTTP状态码。在Web层任意位置抛出这些异常即可:

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
   //
}
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
   //
}

⚠️ 注意:

  • 这些异常应仅用于REST相关层(如Controller/Service)
  • DAO/DAL层不应直接使用
  • 遵循Spring惯例,使用运行时异常(非检查异常)

6.4 使用@ExceptionHandler

另一种方式是在控制器中使用@ExceptionHandler

@ControllerAdvice
public class CustomExceptionHandler {
    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public void handleResourceNotFound() {}
}

❌ 缺点:需在每个控制器中重复定义。更灵活的方案可参考Spring异常处理机制

7. 结论

本文演示了如何使用Spring和Java配置实现REST服务。后续系列将深入探讨API可发现性、高级内容协商及资源多表示形式处理。


原始标题:Build a REST API with Spring and Java Config | Baeldung