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 .