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

Контроллеры, управляемые интерфейсом, в Spring

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

1. Введение

В этом руководстве мы рассмотрим новую функцию Spring MVC, которая позволяет нам указывать веб-запросы с использованием обычных интерфейсов Java.

2. Обзор

Обычно при определении контроллера в Spring MVC мы украшаем его методы различными аннотациями, задающими запрос: URL конечной точки, метод HTTP-запроса, переменные пути и так далее.

Мы можем, например, ввести конечную точку /save/{id} , используя указанные аннотации в обычном простом методе:

@PostMapping("/save/{id}")
@ResponseBody
public Book save(@RequestBody Book book, @PathVariable int id) {
// implementation
}

Естественно, это вообще не проблема, когда у нас всего один контроллер, который обрабатывает запросы. Ситуация немного меняется, когда у нас есть разные контроллеры с одинаковыми сигнатурами методов.

Например, у нас могут быть две разные версии контроллера — из-за миграции или чего-то подобного — с одинаковыми сигнатурами методов. В этом случае у нас будет значительное количество повторяющихся аннотаций, сопровождающих определения методов. Очевидно, что это нарушило бы принцип DRY ( не повторяйся ).

Если бы такая ситуация имела место для чистых классов Java, мы бы просто определили интерфейс и заставили бы классы реализовать этот интерфейс. В контроллерах основная нагрузка на методы приходится не на сигнатуры методов, а на аннотации методов.

Однако Spring 5.1 представила новую функцию:

Аннотации параметров контроллера также обнаруживаются на интерфейсах: возможность полного сопоставления контрактов в интерфейсах контроллера.

Давайте исследуем, как мы можем использовать эту функцию.

3. Интерфейс контроллера

3.1. Настройка контекста

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

В руководстве мы сосредоточимся только на вопросах, связанных с этой функцией. Все вопросы реализации приложения можно найти в нашем репозитории GitHub .

3.2. Интерфейс

Давайте определим обычный интерфейс Java, в котором мы определяем не только сигнатуры методов, но и тип веб-запросов, которые они должны обрабатывать:

@RequestMapping("/default")
public interface BookOperations {

@GetMapping("/")
List<Book> getAll();

@GetMapping("/{id}")
Optional<Book> getById(@PathVariable int id);

@PostMapping("/save/{id}")
public void save(@RequestBody Book book, @PathVariable int id);
}

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

@RestController
@RequestMapping("/book")
public class BookController implements BookOperations {

@Override
public List<Book> getAll() {...}

@Override
public Optional<Book> getById(int id) {...}

@Override
public void save(Book book, int id) {...}

}

Мы по-прежнему должны добавить аннотацию уровня класса @RestController или @Controller к нашему контроллеру. Определенный таким образом контроллер наследует все аннотации, связанные с отображением веб-запросов.

Чтобы убедиться, что контроллер теперь работает как положено, запустим приложение и вызовем метод getAll() , выполнив соответствующий запрос:

curl http://localhost:8081/book/

Несмотря на то, что контроллер реализует интерфейс, мы можем дополнительно его настроить, добавив аннотации веб-запросов. Мы можем сделать это так же, как мы сделали это для интерфейса: либо на уровне класса, либо на уровне метода. Фактически, мы использовали эту возможность при определении контроллера:

@RequestMapping("/book")
public class BookController implements BookOperations {...}

Если мы добавим аннотации веб-запросов в контроллер, они будут иметь приоритет над аннотациями интерфейса. Другими словами, Spring интерпретирует интерфейсы контроллера так же, как Java работает с наследованием.

Мы определяем все общие свойства веб-запросов в интерфейсе, но в контроллере мы всегда можем их точно настроить.

3.3. Предостережение Примечание

Когда у нас есть интерфейс и различные контроллеры, которые его реализуют, мы можем столкнуться с ситуацией, когда веб-запрос может обрабатываться более чем одним методом. Естественно, Spring выдаст исключение:

Caused by: java.lang.IllegalStateException: Ambiguous mapping.

Если мы украсим контроллер @RequestMapping , мы можем снизить риск неоднозначных отображений.

4. Вывод

В этом руководстве мы рассмотрели новую функцию, представленную в Spring 5.1. Теперь, когда контроллеры Spring MVC реализуют интерфейс, они делают это не только стандартным способом Java, но также наследуют все функции, связанные с веб-запросами, определенные в интерфейсе.

Как всегда, мы можем найти соответствующие фрагменты кода в нашем репозитории GitHub .