使用Spring和Java Config构建REST API
翻译来源: Eugen Paraschiv 的 Build a REST API with Spring and Java Config
1.概述
本文介绍如何在Spring中设置REST - 控制器和HTTP响应代码,有效负载编组和内容协商的配置。
2.了解Spring中的REST
Spring框架支持两种创建RESTful服务的方法:
- 将MVC与ModelAndView一起使用
- 使用HTTP消息转换器
ModelAndView方法较旧且记录得更好,但也更冗长,配置更多。 它试图将REST范式转变为旧模型,这并非没有问题。 Spring团队理解这一点,并从Spring 3.0开始提供一流的REST支持。
基于HttpMessageConverter和注解的新方法,更加轻量级且易于实现。配置极少,它为您提供的RESTful服务提供了合理的默认设置。
3.Java 配置
@Configuration
@EnableWebMvc
public class WebConfig{
//
}
新的@EnableWebMvc注解做了一些有用的事情 - 特别是在REST的情况下,它检测类路径上是否存在Jackson和JAXB 2,并自动创建和注册默认的JSON和XML转换器。 注解的功能等同于XML版本:
<mvc:annotation-driven />
这是一个捷径,尽管它在许多情况下可能有用,但它并不完美。 如果需要更复杂的配置,请删除注解并直接扩展WebMvcConfigurationSupport。
3.1 使用 Spring Boot
如果我们使用@SpringBootApplication注解并且spring-webmvc库位于类路径上,那么将使用默认自动配置自动添加@EnableWebMvc注释。
我们仍然可以通过,带有@Configuration注解并实现WebMvcConfigurer接口的类,来为此配置添加MVC功能。 我们还可以使用WebMvcRegistrationsAdapter实例来提供我们自己的RequestMappingHandlerMapping,RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的实现。
最后,如果我们想要丢弃Spring Boot的MVC功能并声明自定义配置,我们可以使用@EnableWebMvc注解来实现。
4.测试Spring上下文
从Spring 3.1开始,我们获得了@Configuration类的一流测试支持:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
classes = {WebConfig.class, PersistenceConfig.class},
loader = AnnotationConfigContextLoader.class)
public class SpringTest {
@Test
public void whenSpringContextIsInstantiated_thenNoExceptions(){
// When
}
}
我们使用@ContextConfiguration注解指定Java配置类。 新的AnnotationConfigContextLoader从@Configuration类加载bean定义。
请注意,Web Config配置类未包含在测试中,因为它需要在未提供的Servlet上下文中运行。
4.1 使用 Spring Boot
Spring Boot提供了几个注释,以更直观的方式为我们的测试设置Spring的ApplicationContext。
我们只能加载应用程序配置的特定片段,或者我们可以模拟整个上下文启动过程。
例如,如果我们想要在不启动服务器的情况下创建整个上下文,我们可以使用@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(...);
}
}
为了避免创建整个上下文,并仅测试我们的MVC控制器,我们可以使用@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(...);
}
}
我们可以在“Spring Boot测试”一文中找到有关此主题的详细信息。
5.控制器
@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;
}
}
Controller实现是非public的 - 这是因为它不需要。
通常,控制器是依赖链中的最后一环。 它接收来自Spring前端控制器(DispatcherServlet)的HTTP请求,并简单地将它们委托给服务层。 如果没有必须通过直接引用注入或操纵控制器的用例,那么我不希望将其声明为public。
请求映射很直截了当。 与任何控制器一样,映射的实际值以及HTTP方法,确定请求的目标方法。 @RequestBody将方法的参数绑定到HTTP请求的主体,而@ResponseBody对响应和返回类型执行相同的操作。
@RestController是在我们的类中包含@ResponseBody和@Controller注解的简写。
它们还确保使用正确的HTTP转换器对资源进行编组和解组。 将进行内容协商以主要基于Accept标头选择将使用哪个活动转换器,尽管也可以使用其他HTTP标头来确定表示。
6.映射HTTP响应代码
HTTP响应的状态代码是REST服务中最重要的部分之一,主题可能很快变得非常复杂。 获得这些权利可以成为提供或破坏服务的原因。
6.1 未映射的请求
如果Spring MVC收到没有映射的请求,它会认为该请求不被允许,并返回405 METHOD NOT ALLOWED返回给客户端。
在将405返回给客户端时包含Allow HTTP头也是一种很好的做法,以指定允许哪些操作。 这是Spring MVC的标准行为,不需要任何其他配置。
6.2 有效的映射请求
对于任何具有映射的请求,Spring MVC认为请求有效,如果没有指定其他状态代码则以200 OK响应。
正因为如此,控制器为创建,更新和删除操作声明了不同的@ResponseStatus,而不是为get,它应该确实返回默认的200 OK。
6.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实践和惯用语。
6.4 使用@ExceptionHandler
在特定状态代码上映射自定义异常的另一个选项,是在控制器中使用@ExceptionHandler注解。 该方法的问题在于注解仅适用于定义它的控制器。 这意味着我们需要在每个控制器中单独声明。
当然,有更多方法可以处理Spring和Spring Boot中的错误,从而提供更大的灵活性。
7.额外的Maven依赖项
除了标准Web应用程序所需的spring-webmvc依赖性之外,我们还需要为REST API设置内容编组和解组:
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
<scope>runtime</scope>
</dependency>
</dependencies>
这些是用于将REST资源的表示转换为JSON或XML的库。
7.1 使用Spring Boot
如果我们想要检索JSON格式的资源,Spring Boot会为不同的库提供支持,即Jackson,Gson和JSON-B。
通过在类路径中包含任何映射库来执行自动配置。
通常,如果我们正在开发Web应用程序,我们只需添加spring-boot-starter-web依赖项并依赖它来包含项目中所有必需的工件:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
Spring Boot默认使用Jackson。
如果我们想要以XML格式序列化我们的资源,我们必须将Jackson XML扩展(jackson-dataformat-xml)添加到我们的依赖项中,或者回退到JAXB实现(默认情况下在JDK中提供),方法是在我们资源上使用@XmlRootElement注解。
8.总结
本教程说明了如何使用Spring和基于Java的配置实现和配置REST服务。
在本系列的下一篇文章中,我将重点介绍API的可发现性,高级内容协商以及使用资源的其他表示。
Github上提供了本文的所有代码。 这是一个基于Maven的项目,因此它应该很容易导入和运行。