Рубрики
Без рубрики

Весеннее сопоставление запросов

Spring @RequestMapping – Базовый пример, @RequestParam, @PathVariable, Сопоставление заголовков

Автор оригинала: Eugen Paraschiv.

1. Обзор

В этом уроке мы сосредоточимся на одной из основных аннотаций в Spring MVC: @RequestMapping.

Проще говоря, аннотация используется для сопоставления веб-запросов с методами контроллера Spring.

Дальнейшее чтение:

Обслуживайте статические ресурсы с помощью Spring

Начало работы с формами в Spring MVC

Конвертеры Http-сообщений с помощью Spring Framework

2. Основы @RequestMapping

Давайте начнем с простого примера: сопоставление HTTP-запроса с методом с использованием некоторых основных критериев.

2.1. @RequestMapping — по пути

@RequestMapping(value = "/ex/foos", method = RequestMethod.GET)
@ResponseBody
public String getFoosBySimplePath() {
    return "Get some Foos";
}

Чтобы проверить это сопоставление с помощью простой команды curl , выполните:

curl -i http://localhost:8080/spring-rest/ex/foos

2.2. @RequestMapping — метод HTTP

Параметр HTTP method не имеет значения по умолчанию. Итак, если мы не зададим значение, оно будет сопоставлено с любым HTTP-запросом.

Вот простой пример, аналогичный предыдущему, но на этот раз сопоставленный с запросом HTTP POST:

@RequestMapping(value = "/ex/foos", method = POST)
@ResponseBody
public String postFoos() {
    return "Post some Foos";
}

Чтобы проверить СООБЩЕНИЕ с помощью команды curl :

curl -i -X POST http://localhost:8080/spring-rest/ex/foos

3. Сопоставление запросов и заголовки HTTP

3.1. @RequestMapping С атрибутом headers

Сопоставление можно сузить еще больше, указав заголовок для запроса:

@RequestMapping(value = "/ex/foos", headers = "key=val", method = GET)
@ResponseBody
public String getFoosWithHeader() {
    return "Get some Foos with Header";
}

Чтобы проверить эту операцию, мы собираемся использовать поддержку заголовка curl :

curl -i -H "key:val" http://localhost:8080/spring-rest/ex/foos

и даже несколько заголовков через headers атрибут @RequestMapping :

@RequestMapping(
  value = "/ex/foos", 
  headers = { "key1=val1", "key2=val2" }, method = GET)
@ResponseBody
public String getFoosWithHeaders() {
    return "Get some Foos with Header";
}

Мы можем проверить это с помощью команды:

curl -i -H "key1:val1" -H "key2:val2" http://localhost:8080/spring-rest/ex/foos

Обратите внимание, что для синтаксиса curl двоеточие разделяет ключ заголовка и значение заголовка, как и в спецификации HTTP, в то время как в Spring используется знак равенства.

3.2. @RequestMapping Потребляет и производит

Особого внимания заслуживает отображение типов носителей, создаваемых контроллером методом.

Мы можем сопоставить запрос на основе его заголовка Accept с помощью атрибута @RequestMapping | headers , введенного выше:

@RequestMapping(
  value = "/ex/foos", 
  method = GET, 
  headers = "Accept=application/json")
@ResponseBody
public String getFoosAsJsonFromBrowser() {
    return "Get some Foos with Header Old";
}

Сопоставление для этого способа определения заголовка Accept является гибким — он использует contains вместо equals, поэтому запрос, такой как следующий, все равно будет отображаться правильно:

curl -H "Accept:application/json,text/html" 
  http://localhost:8080/spring-rest/ex/foos

Начиная с Spring 3.1, в аннотации @RequestMapping теперь есть производит и потребляет атрибуты , специально для этой цели:

@RequestMapping(
  value = "/ex/foos", 
  method = RequestMethod.GET, 
  produces = "application/json"
)
@ResponseBody
public String getFoosAsJsonFromREST() {
    return "Get some Foos with Header New";
}

Кроме того, старый тип сопоставления с атрибутом headers будет автоматически преобразован в новый механизм produces , начиная с Spring 3.1, поэтому результаты будут идентичны.

Это потребляется через curl таким же образом:

curl -H "Accept:application/json" 
  http://localhost:8080/spring-rest/ex/foos

Кроме того, производит также поддерживает несколько значений:

@RequestMapping(
  value = "/ex/foos", 
  method = GET,
  produces = { "application/json", "application/xml" }
)

Имейте в виду, что это — старый и новый способы указания заголовка Accept — в основном одно и то же сопоставление, поэтому Spring не позволит им объединяться.

Если оба этих метода будут активны, это приведет к:

Caused by: java.lang.IllegalStateException: Ambiguous mapping found. 
Cannot map 'fooController' bean method 
java.lang.String 
org.baeldung.spring.web.controller
  .FooController.getFoosAsJsonFromREST()
to 
{ [/ex/foos],
  methods=[GET],params=[],headers=[],
  consumes=[],produces=[application/json],custom=[]
}: 
There is already 'fooController' bean method
java.lang.String 
org.baeldung.spring.web.controller
  .FooController.getFoosAsJsonFromBrowser() 
mapped.

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

И, конечно, если вы хотите углубиться в создание API REST с Spring, проверьте новый курс REST с Spring .

4. Сопоставление Запросов С Переменными Пути

Части URI отображения могут быть привязаны к переменным с помощью аннотации @PathVariable .

4.1. Single @PathVariable

Простой пример с одной переменной пути:

@RequestMapping(value = "/ex/foos/{id}", method = GET)
@ResponseBody
public String getFoosBySimplePathWithPathVariable(
  @PathVariable("id") long id) {
    return "Get a specific Foo with id=" + id;
}

Это можно проверить с помощью curl :

curl http://localhost:8080/spring-rest/ex/foos/1

Если имя параметра метода точно совпадает с именем переменной path, то это можно упростить, используя | @PathVariable без значения :

@RequestMapping(value = "/ex/foos/{id}", method = GET)
@ResponseBody
public String getFoosBySimplePathWithPathVariable(
  @PathVariable String id) {
    return "Get a specific Foo with id=" + id;
}

Обратите внимание, что @PathVariable извлекает выгоду из автоматического преобразования типов, поэтому мы могли бы также объявить id как:

@PathVariable long id

4.2. Несколько @PathVariable

Более сложный URI может потребоваться сопоставить несколько частей URI с несколькими значениями :

@RequestMapping(value = "/ex/foos/{fooid}/bar/{barid}", method = GET)
@ResponseBody
public String getFoosBySimplePathWithPathVariables
  (@PathVariable long fooid, @PathVariable long barid) {
    return "Get a specific Bar with id=" + barid + 
      " from a Foo with id=" + fooid;
}

Это легко проверить с помощью curl таким же образом:

curl http://localhost:8080/spring-rest/ex/foos/1/bar/2

4.3. @PathVariable С Регулярным Выражением

Регулярные выражения также можно использовать при сопоставлении @PathVariable.

Например, мы ограничим отображение только числовыми значениями для id :

@RequestMapping(value = "/ex/bars/{numericId:[\\d]+}", method = GET)
@ResponseBody
public String getBarsBySimplePathWithPathVariable(
  @PathVariable long numericId) {
    return "Get a specific Bar with id=" + numericId;
}

Это будет означать, что следующие URL-адреса будут совпадать:

http://localhost:8080/spring-rest/ex/bars/1

Но этого не будет:

http://localhost:8080/spring-rest/ex/bars/abc

5. Сопоставление Запросов С Параметрами Запроса

@RequestMapping позволяет легко сопоставлять параметры URL-адреса с @RequestParam аннотацией .

Теперь мы сопоставляем запрос с URI:

http://localhost:8080/spring-rest/ex/bars?id=100
@RequestMapping(value = "/ex/bars", method = GET)
@ResponseBody
public String getBarBySimplePathWithRequestParam(
  @RequestParam("id") long id) {
    return "Get a specific Bar with id=" + id;
}

Затем мы извлекаем значение параметра id с помощью аннотации @RequestParam(“id”) в сигнатуре метода контроллера.

Чтобы отправить запрос с параметром id , мы будем использовать поддержку параметров в curl :

curl -i -d id=100 http://localhost:8080/spring-rest/ex/bars

В этом примере параметр был привязан напрямую, без предварительного объявления.

Для более сложных сценариев @RequestMapping может дополнительно определить параметры как еще один способ сужения сопоставления запросов:

@RequestMapping(value = "/ex/bars", params = "id", method = GET)
@ResponseBody
public String getBarBySimplePathWithExplicitRequestParam(
  @RequestParam("id") long id) {
    return "Get a specific Bar with id=" + id;
}

Допускаются еще более гибкие сопоставления. Можно задать несколько значений params , и не все из них должны использоваться:

@RequestMapping(
  value = "/ex/bars", 
  params = { "id", "second" }, 
  method = GET)
@ResponseBody
public String getBarBySimplePathWithExplicitRequestParams(
  @RequestParam("id") long id) {
    return "Narrow Get a specific Bar with id=" + id;
}

И, конечно же, запрос к URI, такому как:

http://localhost:8080/spring-rest/ex/bars?id=100&second=something

всегда будет отображаться на лучшее совпадение, которое является более узким совпадением, которое определяет как id , так и второй параметр.

6. Угловые случаи сопоставления запросов

6.1. @RequestMapping — Несколько путей, сопоставленных одному и тому же методу контроллера

Хотя для одного метода контроллера обычно используется одно значение @RequestMapping path (просто хорошая практика, а не жесткое и быстрое правило), в некоторых случаях может потребоваться сопоставление нескольких запросов с одним и тем же методом.

В этом случае значение атрибута @RequestMapping принимает несколько сопоставлений , а не только одно:

@RequestMapping(
  value = { "/ex/advanced/bars", "/ex/advanced/foos" }, 
  method = GET)
@ResponseBody
public String getFoosOrBarsByPath() {
    return "Advanced - Get some Foos or Bars";
}

Теперь обе эти команды curl должны использовать один и тот же метод:

curl -i http://localhost:8080/spring-rest/ex/advanced/foos
curl -i http://localhost:8080/spring-rest/ex/advanced/bars

6.2. @RequestMapping — Несколько методов HTTP-запроса к одному и тому же методу контроллера

Несколько запросов, использующих разные HTTP-глаголы, могут быть сопоставлены одному и тому же методу контроллера:

@RequestMapping(
  value = "/ex/foos/multiple", 
  method = { RequestMethod.PUT, RequestMethod.POST }
)
@ResponseBody
public String putAndPostFoos() {
    return "Advanced - PUT and POST within single method";
}

С помощью curl оба они теперь будут использовать один и тот же метод:

curl -i -X POST http://localhost:8080/spring-rest/ex/foos/multiple
curl -i -X PUT http://localhost:8080/spring-rest/ex/foos/multiple

6.3. @RequestMapping — резервный вариант для всех запросов

Чтобы реализовать простой резерв для всех запросов, использующих определенный метод HTTP, например, для GET:

@RequestMapping(value = "*", method = RequestMethod.GET)
@ResponseBody
public String getFallback() {
    return "Fallback for GET Requests";
}

или даже для всех запросов:

@RequestMapping(
  value = "*", 
  method = { RequestMethod.GET, RequestMethod.POST ... })
@ResponseBody
public String allFallback() {
    return "Fallback for All Requests";
}

6.4. Неоднозначная ошибка отображения

Ошибка неоднозначного сопоставления возникает, когда Spring оценивает два или более сопоставления запросов как одинаковые для разных методов контроллера. Сопоставление запросов одно и то же, если оно имеет один и тот же метод HTTP, URL-адрес, параметры, заголовки и тип носителя.

Например, это неоднозначное отображение:

@GetMapping(value = "foos/duplicate" )
public String duplicate() {
    return "Duplicate";
}

@GetMapping(value = "foos/duplicate" )
public String duplicateEx() {
    return "Duplicate";
}

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

Caused by: java.lang.IllegalStateException: Ambiguous mapping.
  Cannot map 'fooMappingExamplesController' method 
  public java.lang.String org.baeldung.web.controller.FooMappingExamplesController.duplicateEx()
  to {[/ex/foos/duplicate],methods=[GET]}:
  There is already 'fooMappingExamplesController' bean method
  public java.lang.String org.baeldung.web.controller.FooMappingExamplesController.duplicate() mapped.

Внимательное прочтение сообщения об ошибке указывает на то, что Spring не может сопоставить метод org.baeldung.web.controller.FooMappingExamplesController.duplicateEx(), поскольку он имеет конфликтное сопоставление с уже сопоставленным org.baeldung.web.controller.FooMappingExamplesController.duplicate().

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

@GetMapping(value = "foos/duplicate", produces = MediaType.APPLICATION_XML_VALUE)
public String duplicateXml() {
    return "Duplicate";
}
    
@GetMapping(value = "foos/duplicate", produces = MediaType.APPLICATION_JSON_VALUE)
public String duplicateJson() {
    return "{\"message\":\"Duplicate\"}";
}

Это различие позволяет нашему контроллеру возвращать правильное представление данных на основе заголовка Accepts , предоставленного в запросе.

Другой способ решить эту проблему-обновить URL-адрес, назначенный любому из двух задействованных методов.

7. Новые ярлыки сопоставления запросов

Spring Framework 4.3 представила несколько новых аннотаций сопоставления HTTP, все они основаны на @RequestMapping :

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @@Pathmapping

Эти новые аннотации могут улучшить читабельность и уменьшить многословие кода.

Давайте рассмотрим эти новые аннотации в действии, создав RESTful API, поддерживающий операции CRUD:

@GetMapping("/{id}")
public ResponseEntity getBazz(@PathVariable String id){
    return new ResponseEntity<>(new Bazz(id, "Bazz"+id), HttpStatus.OK);
}

@PostMapping
public ResponseEntity newBazz(@RequestParam("name") String name){
    return new ResponseEntity<>(new Bazz("5", name), HttpStatus.OK);
}

@PutMapping("/{id}")
public ResponseEntity updateBazz(
  @PathVariable String id,
  @RequestParam("name") String name) {
    return new ResponseEntity<>(new Bazz(id, name), HttpStatus.OK);
}

@DeleteMapping("/{id}")
public ResponseEntity deleteBazz(@PathVariable String id){
    return new ResponseEntity<>(new Bazz(id), HttpStatus.OK);
}

Глубокое погружение в них можно найти здесь .

8. Конфигурация Пружины

Конфигурация Spring MVC достаточно проста, учитывая, что наш FooController определен в следующем пакете:

package org.baeldung.spring.web.controller;

@Controller
public class FooController { ... }

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

@Configuration
@EnableWebMvc
@ComponentScan({ "org.baeldung.spring.web.controller" })
public class MvcConfig {
    //
}

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

Эта статья была посвящена @RequestMapping аннотации в Spring , обсуждая простой случай использования, сопоставление заголовков HTTP, связывание частей URI с @PathVariable и работу с параметрами URI и аннотацией @RequestParam .

Если вы хотите узнать, как использовать другую основную аннотацию в Spring MVC, вы можете изучить аннотацию @ModelAttribute здесь .

Полный код из статьи доступен на GitHub .