Перейти к основному содержимому

Создайте REST API с помощью Spring и Java Config

· 7 мин. чтения

1. Обзор

В этом руководстве мы узнаем, как настроить REST в Spring, включая коды ответов контроллера и HTTP, настройку сортировки полезной нагрузки и согласование содержимого.

2. Понимание REST в Spring

Платформа Spring поддерживает два способа создания сервисов RESTful:

  • использование MVC с ModelAndView
  • с помощью конвертеров HTTP-сообщений

Подход ModelAndView устарел и гораздо лучше документирован, но также более многословен и требует сложной настройки. Он пытается внедрить парадигму REST в старую модель, что не без проблем. Команда Spring поняла это и предоставила первоклассную поддержку REST, начиная с Spring 3.0.

Новый подход, основанный на HttpMessageConverter и аннотациях, намного проще и проще в реализации. Конфигурация минимальна и обеспечивает разумные значения по умолчанию для того, что мы ожидаем от службы RESTful.

3. Конфигурация Java

@Configuration
@EnableWebMvc
public class WebConfig{
//
}

Новая аннотация @EnableWebMvc делает несколько полезных вещей; в частности, в случае REST он обнаруживает наличие Jackson и JAXB 2 в пути к классам и автоматически создает и регистрирует конвертеры JSON и XML по умолчанию. Функциональность аннотации эквивалентна XML-версии:

<mvc: на основе аннотаций />

Это короткий путь, и хотя он может быть полезен во многих ситуациях, он не идеален. Когда нам нужна более сложная конфигурация, мы можем удалить аннотацию и напрямую расширить WebMvcConfigurationSupport .

3.1. Использование весенней загрузки

Если мы используем аннотацию @SpringBootApplication , а библиотека spring-webmvc находится в пути к классам, то аннотация @EnableWebMvc добавляется автоматически с автонастройкой по умолчанию .

Мы по-прежнему можем добавить функциональность MVC в эту конфигурацию, реализовав интерфейс WebMvcConfigurer в аннотированном классе @Configuration . Мы также можем использовать экземпляр 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 SpringContextIntegrationTest {

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

Мы указываем классы конфигурации Java с помощью аннотации @ContextConfiguration . Новый AnnotationConfigContextLoader загружает определения компонентов из классов @Configuration .

Обратите внимание, что класс конфигурации WebConfig не включен в тест, поскольку он должен работать в контексте сервлета, который не предоставляется.

4.1. Использование весенней загрузки

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. Для целей этой статьи контроллер моделирует простой ресурс 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);
}

}

Как мы видим, мы используем простую утилиту RestPreconditions в стиле Guava :

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

Реализация контроллера не является общедоступной, потому что в этом нет необходимости.

Обычно контроллер является последним в цепочке зависимостей. Он получает HTTP-запросы от фронт-контроллера Spring ( DispatcherServlet ) и просто делегирует их на уровень службы. Если нет случая использования, когда контроллер должен быть внедрен или управляться через прямую ссылку, мы можем предпочесть не объявлять его общедоступным.

Сопоставления запросов просты. Как и в случае с любым контроллером, фактическое значение сопоставления, а также метод HTTP определяют целевой метод для запроса. @RequestBody привяжет параметры метода к телу HTTP-запроса, тогда как @ResponseBody сделает то же самое для ответа и возвращаемого типа.

@RestController — это сокращение для включения аннотаций @ResponseBody и @Controller в наш класс . ``

Они также гарантируют, что ресурс будет маршалироваться и демаршалироваться с использованием правильного преобразователя HTTP. Будет происходить согласование содержимого, чтобы выбрать, какой из активных преобразователей будет использоваться, в основном на основе заголовка Accept , хотя для определения представления также могут использоваться другие заголовки HTTP.

6. Сопоставление кодов ответов HTTP

Коды состояния ответа HTTP — одна из самых важных частей службы REST, и тема может быстро стать очень сложной. Правильное их выполнение может быть тем, что создает или разрушает сервис.

6.1. Несопоставленные запросы

Если Spring MVC получает запрос, который не имеет сопоставления, он считает запрос неразрешенным и возвращает клиенту 405 METHOD NOT ALLOWED.

Также рекомендуется включать HTTP- заголовок Allow при возврате 405 клиенту, чтобы указать, какие операции разрешены. Это стандартное поведение Spring MVC, которое не требует дополнительной настройки.

6.2. Действительные сопоставленные запросы

Для любого запроса, который имеет сопоставление, Spring MVC считает запрос действительным и отвечает 200 OK, если не указан другой код состояния.

Именно из-за этого контроллер объявляет разные @ResponseStatus для действий create , update и delete , но не для get , которые действительно должны возвращать значение по умолчанию 200 OK.

6.3. Ошибка клиента

В случае ошибки клиента определяются пользовательские исключения, которые сопоставляются с соответствующими кодами ошибок.

Простое выбрасывание этих исключений из любого уровня веб-уровня гарантирует, что 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 ** **

В дополнение к зависимости 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. Использование весенней загрузки

Если мы хотим получить ресурсы в формате JSON, Spring Boot обеспечивает поддержку различных библиотек, а именно Jackson, Gson и JSON-B.

Мы можем выполнить автоматическую настройку, просто включив любую из библиотек сопоставления в путь к классам.

Обычно, если мы разрабатываем веб-приложение, мы просто добавляем зависимость spring-boot-starter-web и полагаемся на нее, чтобы включить все необходимые артефакты в наш проект :

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

Spring Boot по умолчанию использует Jackson.

Если мы хотим сериализовать наши ресурсы в формате XML, нам придется добавить расширение Джексона XML ( jackson-dataformat-xml ) к нашим зависимостям или вернуться к реализации JAXB (предоставленной по умолчанию в JDK) с помощью Аннотация @XmlRootElement на нашем ресурсе.

8. Заключение

В этой статье показано, как реализовать и настроить службу REST с помощью Spring и конфигурации на основе Java.

В следующих статьях этой серии мы сосредоточимся на возможности обнаружения API , расширенном согласовании содержимого и работе с дополнительными представлениями ресурса.

Весь код в этой статье доступен на Github . Это проект на основе Maven, поэтому его легко импортировать и запускать как есть.