Build a REST API with Spring and Java Config – 用Spring和Java配置构建REST API

最后修改: 2014年 11月 4日

1. Overview

1.概述

In this tutorial, we’ll learn how to set up REST in Spring, including the Controller and HTTP response codes, configuration of payload marshalling, and content negotiation.

在本教程中,我们将学习如何在Spring中设置REST,包括控制器和HTTP响应代码,配置有效载荷的编排,以及内容协商。

2. Understanding REST in Spring

2.了解Spring中的REST

The Spring framework supports two ways of creating RESTful services:

Spring框架支持两种创建RESTful服务的方式。

  • using MVC with ModelAndView
  • using HTTP message converters

The ModelAndView approach is older and much better documented, but also more verbose and configuration heavy. It tries to shoehorn the REST paradigm into the old model, which isn’t without problems. The Spring team understood this, and provided first-class REST support starting with Spring 3.0.

ModelAndView方法更古老,有更好的文档,但也更啰嗦,配置也更重。它试图将REST范式塞进旧的模型中,这并非没有问题。Spring团队了解这一点,并从Spring 3.0开始提供一流的REST支持。

The new approach, based on HttpMessageConverter and annotations, is much more lightweight and easy to implement. Configuration is minimal, and it provides sensible defaults for what we would expect from a RESTful service.

基于HttpMessageConverter 和注解的新方法更加轻量级且易于实现。配置是最小的,它为我们从RESTful服务中期望的东西提供了合理的默认值。

3. The Java Configuration

3.Java配置

@Configuration
@EnableWebMvc
public class WebConfig{
   //
}

The new @EnableWebMvc annotation does some useful things; specifically, in the case of REST, it detects the existence of Jackson and JAXB 2 on the classpath, and automatically creates and registers default JSON and XML converters. The functionality of the annotation is equivalent to the XML version:

新的@EnableWebMvc注解做了一些有用的事情;具体而言,在REST的情况下,它检测到classpath上存在Jackson和JAXB 2,并自动创建和注册默认的JSON和XML转换器。该注解的功能与XML版本相当。

<mvc:annotation-driven />

<mvc:注释驱动 />

This is a shortcut, and though it may be useful in many situations, it’s not perfect. When we need a more complex configuration, we can remove the annotation and extend WebMvcConfigurationSupport directly.

这是一个捷径,尽管它在很多情况下可能很有用,但它并不完美。当我们需要更复杂的配置时,我们可以删除注释并直接扩展WebMvcConfigurationSupport

3.1. Using Spring Boot

3.1.使用Spring Boot

If we’re using the @SpringBootApplication annotation, and the spring-webmvc library is on the classpath, then the @EnableWebMvc annotation is added automatically with a default autoconfiguration.

如果我们使用@SpringBootApplication注解,并且spring-webmvc库在classpath上,那么@EnableWebMvc注解将被自动添加到默认自动配置

We can still add MVC functionality to this configuration by implementing the WebMvcConfigurer interface on a @Configuration annotated class. We can also use a WebMvcRegistrationsAdapter instance to provide our own RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver implementations.

我们仍然可以通过在@Configuration 注释的类上实现WebMvcConfigurer 接口来为该配置添加 MVC 功能。我们还可以使用WebMvcRegistrationsAdapter实例来提供我们自己的RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver的实现。

Finally, if we want to discard Spring Boot’s MVC features and declare a custom configuration, we can do so by using the @EnableWebMvc annotation.

最后,如果我们想摒弃Spring Boot的MVC功能,并声明一个自定义配置,我们可以通过使用@EnableWebMvc注解来实现。

4. Testing the Spring Context

4.测试Spring上下文

Starting with Spring 3.1, we get first-class testing support for @Configuration classes:

从Spring 3.1开始,我们得到了对@Configuration类的一流测试支持。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration( 
  classes = {WebConfig.class, PersistenceConfig.class},
  loader = AnnotationConfigContextLoader.class)
public class SpringContextIntegrationTest {

   @Test
   public void contextLoads(){
      // When
   }
}

We’re specifying the Java configuration classes with the @ContextConfiguration annotation. The new AnnotationConfigContextLoader loads the bean definitions from the @Configuration classes.

我们用@ContextConfiguration注解来指定Java配置类。新的AnnotationConfigContextLoader@Configuration类中加载bean定义。

Notice that the WebConfig configuration class isn’t included in the test because it needs to run in a Servlet context, which isn’t provided.

注意,WebConfig配置类没有包含在测试中,因为它需要在Servlet上下文中运行,而Servlet没有提供。

4.1. Using Spring Boot

4.1.使用Spring Boot

Spring Boot provides several annotations to set up the Spring ApplicationContext for our tests in a more intuitive way.

Spring Boot提供了几个注解,以更直观的方式为我们的测试设置Spring ApplicationContext

We can load only a particular slice of the application configuration, or we can simulate the whole context startup process.

我们可以只加载应用程序配置的一个特定片断,也可以模拟整个上下文启动过程。

For instance, we can use the @SpringBootTest annotation if we want to create the entire context without starting the server.

例如,如果我们想在不启动服务器的情况下创建整个上下文,我们可以使用@SpringBootTest注解。

With that in place, we can then add the @AutoConfigureMockMvc to inject a MockMvc instance and send HTTP requests:

有了这些,我们就可以添加@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(...);
    }

}

To avoid creating the whole context and test only our MVC Controllers, we can use @WebMvcTest:

为了避免创建整个上下文,只测试我们的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(...);
    }
}

We can find detailed information on this subject in our ‘Testing in Spring Boot’ article.

我们可以在我们的 “Spring Boot中的测试 “文章中找到关于这一主题的详细信息。

5. The Controller

5.控制器

The @RestController is the central artifact in the entire Web Tier of the RESTful API. For the purpose of this article, the controller is modeling a simple REST resource, Foo:

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

}

As we can see, we’re using a straightforward, Guava style RestPreconditions utility:

正如我们所看到的,我们正在使用一个直接的、Guava风格的RestPreconditions工具。

public class RestPreconditions {
    public static <T> T checkFound(T resource) {
        if (resource == null) {
            throw new MyResourceNotFoundException();
        }
        return resource;
    }
}

The Controller implementation is non-public because it doesn’t need to be.

控制器的实现是不公开的,因为它不需要公开。

Usually, the controller is the last in the chain of dependencies. It receives HTTP requests from the Spring front controller (the DispatcherServlet), and simply delegates them forward to a service layer. If there’s no use case where the controller has to be injected or manipulated through a direct reference, then we may prefer not to declare it as public.

通常情况下,控制器是依赖关系链中的最后一个。它从Spring前台控制器(DispatcherServlet)接收HTTP请求,并简单地将其转发到服务层。如果没有任何用例需要通过直接引用来注入或操作控制器,那么我们最好不要将其声明为public。

The request mappings are straightforward. As with any controller, the actual value of the mapping, as well as the HTTP method, determine the target method for the request. @RequestBody will bind the parameters of the method to the body of the HTTP request, whereas @ResponseBody does the same for the response and return type.

与任何控制器一样,映射的实际以及HTTP方法决定了请求的目标方法。@RequestBody将把方法的参数绑定到HTTP请求的主体上,而@ResponseBody对响应和返回类型也是如此。

The @RestController is a shorthand to include both the @ResponseBody and the @Controller annotations in our class.

@RestController 是一个速记,在我们的类中包含@ResponseBody@Controller 注释

They also ensure that the resource will be marshalled and unmarshalled using the correct HTTP converter. Content negotiation will take place to choose which one of the active converters will be used, based mostly on the Accept header, although other HTTP headers may be used to determine the representation as well.

它们还确保资源将使用正确的HTTP转换器被调集和取消调集。内容协商将发生,以选择使用哪一个活跃的转换器,主要基于Accept头,尽管其他HTTP头也可能被用来确定表示。

6. Mapping the HTTP Response Codes

6.HTTP响应代码的映射

The status codes of the HTTP response are one of the most important parts of the REST service, and the subject can quickly become very complicated. Getting these right can be what makes or breaks the service.

HTTP响应的状态码是REST服务中最重要的部分之一,而且这个主题可以很快变得非常复杂。把握好这些可能是服务的成败所在。

6.1. Unmapped Requests

6.1.未映射的请求

If Spring MVC receives a request which doesn’t have a mapping, it considers the request not allowed, and returns a 405 METHOD NOT ALLOWED back to the client.

如果Spring MVC收到一个没有映射的请求,它认为这个请求是不允许的,并向客户端返回405 METHOD NOT ALLOWED。

It’s also good practice to include the Allow HTTP header when returning a 405 to the client to specify which operations are allowed. This is the standard behavior of Spring MVC, and doesn’t require any additional configuration.

在向客户端返回405时,包含Allow HTTP头也是很好的做法,以指定允许哪些操作。这是Spring MVC的标准行为,不需要任何额外的配置。

6.2. Valid Mapped Requests

6.2.有效的映射请求

For any request that does have a mapping, Spring MVC considers the request valid and responds with 200 OK, if no other status code is otherwise specified.

对于任何确实有映射的请求,Spring MVC认为该请求是有效的,如果没有指定其他状态代码,则以200 OK回应。

It’s because of this that the controller declares different @ResponseStatus for the create, update and delete actions, but not for get, which should indeed return the default 200 OK.

正因为如此,控制器为创建更新删除动作声明了不同的@ResponseStatus,但没有为获取声明,后者确实应该返回默认的200 OK。

6.3. Client Error

6.3.客户端错误

In the case of a client error, custom exceptions are defined and mapped to the appropriate error codes.

在客户端出错的情况下,自定义异常被定义并映射到相应的错误代码。

Simply throwing these exceptions from any of the layers of the web tier will ensure Spring maps the corresponding status code on the HTTP response:

只要从Web层的任何一层抛出这些异常,就能确保Spring在HTTP响应上映射出相应的状态代码。

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

These exceptions are part of the REST API, and as such, we should only use them in the appropriate layers corresponding to REST; for instance, if a DAO/DAL layer exists, it shouldn’t use the exceptions directly.

这些异常是REST API的一部分,因此,我们应该只在与REST相对应的适当层中使用它们;例如,如果存在一个DAO/DAL层,它不应该直接使用这些异常。

Note also that these aren’t checked exceptions, but runtime exceptions in line with Spring practices and idioms.

还要注意的是,这些并不是检查过的异常,而是符合Spring惯例和习惯的运行时异常。

6.4. Using @ExceptionHandler

6.4.使用@ExceptionHandler

Another option to map custom exceptions on specific status codes is to use the @ExceptionHandler annotation in the controller. The problem with that approach is that the annotation only applies to the controller in which it’s defined. This means that we need to declare them in each controller individually.

另一个选择是在控制器中使用@ExceptionHandler注解来映射特定状态代码上的自定义异常。这种方法的问题是,该注解只适用于它所定义的控制器。这意味着,我们需要在每个控制器中单独声明它们。

Of course, there are more ways to handle errors in both Spring and Spring Boot that offer more flexibility.

当然,在Spring和Spring Boot中还有更多的处理错误的方法,提供更多的灵活性。

7. Additional Maven Dependencies

7.额外的Maven依赖性

In addition to the spring-webmvc dependency required for the standard web application, we’ll need to set up content marshalling and unmarshalling for the REST API:

除了spring-webmvc依赖性标准Web应用程序所需的之外,我们还需要为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>

These are the libraries we’ll use to convert the representation of the REST resource to either JSON or XML.

这些是我们用来将REST资源的表示方法转换为JSON或XML的库。

7.1. Using Spring Boot

7.1.使用Spring Boot

If we want to retrieve JSON-formatted resources, Spring Boot provides support for different libraries, namely Jackson, Gson, and JSON-B.

如果我们想检索JSON格式的资源,Spring Boot提供了对不同库的支持,即Jackson、Gson和JSON-B。

We can carry out auto-configuration by simply including any of the mapping libraries in the classpath.

我们可以通过简单地在classpath中包括任何一个映射库来进行自动配置。

Usually, if we’re developing a web application, we’ll just add the spring-boot-starter-web dependency and rely on it to include all the necessary artifacts to our project:

通常,如果我们正在开发一个Web应用程序,我们只需添加spring-boot-starter-web依赖,并依靠它将所有必要的工件纳入我们的项目

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

Spring Boot uses Jackson by default.

Spring Boot默认使用Jackson。

If we want to serialize our resources in an XML format, we’ll have to add the Jackson XML extension (jackson-dataformat-xml) to our dependencies, or fallback to the JAXB implementation (provided by default in the JDK) by using the @XmlRootElement annotation on our resource.

如果我们想以XML格式序列化我们的资源,我们必须在我们的依赖中添加Jackson XML扩展(jackson-dataformat-xml),或者通过在我们的资源上使用@XmlRootElement注解而退回到JAXB实现(在JDK中默认提供)。

8. Conclusion

8.结论

This article illustrated how to implement and configure a REST Service using Spring and Java-based configuration.

本文说明了如何使用Spring和基于Java的配置来实现和配置一个REST服务。

In the next articles in the series, we’ll focus on Discoverability of the API, advanced content negotiation, and working with additional representations of a Resource.

在该系列的下一篇文章中,我们将重点讨论API 的可识别性、高级内容协商以及使用资源的额外表示法。

All of the code in this article is available over on Github. This is a Maven-based project, so it should be easy to import and run as it is.

本文中的所有代码都可以在Github上找到。这是一个基于Maven的项目,所以应该很容易导入并按原样运行。