Статьи

Создание веб-службы RESTful с использованием Spring 3.1 и конфигурации на основе Java, часть 2

1. Обзор

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

2. Понимание REST весной

Среда Spring поддерживает 2 способа создания сервисов RESTful:

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

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

Новый подход, основанный на HttpMessageConverter и аннотациях , гораздо более легкий и простой в реализации. Конфигурация минимальна и обеспечивает разумные значения по умолчанию для того, что вы ожидаете от сервиса RESTful. Это, однако, новее и немного легковеснее в отношении документации; Более того, ссылка не выходит за пределы возможного, чтобы сделать различия и компромиссы между двумя подходами настолько ясными, насколько они должны быть. Тем не менее, именно так сервисы RESTful должны быть собраны после Spring 3.0.

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

1
2
3
4
5
@Configuration
@EnableWebMvc
public class WebConfig{
   //
}

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

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

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

4. Тестирование контекста Spring

Начиная с Spring 3.1 , мы получаем первоклассную поддержку тестирования классов @Configuration :

01
02
03
04
05
06
07
08
09
10
@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration( classes = { ApplicationConfig.class, PersistenceConfig.class },
 loader = AnnotationConfigContextLoader.class )
public class SpringTest{
 
   @Test
   public void whenSpringContextIsInstantiated_thenNoExceptions(){
      // When
   }
}

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

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

5. Контроллер

@Controller — это центральный артефакт всего веб-уровня API RESTful. Для этой цели контроллер моделирует простой ресурс REST — Foo :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Controller
@RequestMapping( value = "foo" )
class FooController{
 
   @Autowired
   IFooService service;
 
   @RequestMapping( method = RequestMethod.GET )
   @ResponseBody
   public List< Foo > getAll(){
      return service.getAll();
   }
 
   @RequestMapping( value = "/{id}", method = RequestMethod.GET )
   @ResponseBody
   public Foo get( @PathVariable( "id" ) Long id ){
      return RestPreconditions.checkNotNull( service.getById( id ) );
   }
 
   @RequestMapping( method = RequestMethod.POST )
   @ResponseStatus( HttpStatus.CREATED )
   @ResponseBody
   public Long create( @RequestBody Foo entity ){
      RestPreconditions.checkNotNullFromRequest( entity );
      return service.create( entity );
   }
 
   @RequestMapping( method = RequestMethod.PUT )
   @ResponseStatus( HttpStatus.OK )
   public void update( @RequestBody Foo entity ){
      RestPreconditions.checkNotNullFromRequest( entity );
      RestPreconditions.checkNotNull( service.getById( entity.getId() ) );
      service.update( entity );
   }
 
   @RequestMapping( value = "/{id}", method = RequestMethod.DELETE )
   @ResponseStatus( HttpStatus.OK )
   public void delete( @PathVariable( "id" ) Long id ){
      service.deleteById( id );
   }
 
}

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

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

6. Отображение кодов ответов HTTP

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

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

Если Spring MVC получает запрос, который не имеет сопоставления, он считает, что запрос не разрешен, и возвращает МЕТОД 405, НЕ РАЗРЕШЕННЫЙ обратно клиенту. Также рекомендуется включать заголовок Allow HTTP при возврате 405 клиенту, чтобы указать, какие операции разрешены. Это стандартное поведение Spring MVC и не требует дополнительной настройки.

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

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

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

В случае ошибки клиента настраиваемые исключения определяются и сопоставляются с соответствующими кодами ошибок. Простое выбрасывание этих исключений из любого уровня веб-уровня гарантирует, что Spring отобразит соответствующий код состояния в ответе HTTP.

1
2
3
4
5
6
7
8
@ResponseStatus( value = HttpStatus.BAD_REQUEST )
public class BadRequestException extends RuntimeException{
   //
}
@ResponseStatus( value = HttpStatus.NOT_FOUND )
public class ResourceNotFoundException extends RuntimeException{
   //
}

Эти исключения являются частью REST API и, как таковые, должны использоваться только на соответствующих уровнях, соответствующих REST; если, например, существует слой DAO / DAL, он не должен использовать исключения напрямую. Также обратите внимание, что это не проверенные исключения, а исключения времени выполнения — в соответствии с практикой и идиомами Spring.

6.4. Использование @ExceptionHandler

Другой вариант сопоставления пользовательских исключений с конкретными кодами состояния — использовать аннотацию @ExceptionHandler в контроллере. Проблема с этим подходом состоит в том, что аннотация применяется только к контроллеру, в котором она определена, а не ко всему контейнеру Spring, что означает, что она должна быть объявлена ​​в каждом контроллере индивидуально. Это быстро становится громоздким, особенно в более сложных приложениях, где много контроллеров. В настоящее время в Spring решено несколько проблем JIRA для устранения этого и других связанных ограничений: SPR-8124 , SPR-7278 , SPR-8406 .

7. Дополнительные зависимости Maven

В дополнение к зависимости spring-webmvc, необходимой для стандартного веб-приложения , нам нужно настроить маршалинг и демаршаллинг контента для REST API:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
<dependencies>
   <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson.version}</version>
   </dependency>
   <dependency>
      <groupId>javax.xml.bind</groupId>
      <artifactId>jaxb-api</artifactId>
      <version>${jaxb-api.version}</version>
      <scope>runtime</scope>
   </dependency>
</dependencies>
 
<properties>
   <jackson.version>2.2.2</jackson.version>
   <jaxb-api.version>2.2.9</jaxb-api.version>
</properties>

Это библиотеки, используемые для преобразования представления ресурса REST в JSON или XML .

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

В этом посте рассказывалось о настройке и реализации службы RESTful с использованием Spring 3.1 и конфигурации на основе Java, обсуждались коды ответов HTTP, согласование базового содержимого и маршалинг.

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

Справка: создайте REST API с помощью Spring 3 и Java Config от нашего партнера по JCG Юджина Параскива в блоге baeldung .