Spring RequestMapping

Spring RequestMapping

1. обзор

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

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

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

Служите статическим ресурсам с помощью Spring

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

Read more

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

Узнайте, как работать с формами с помощью Spring MVC - отображение базовой сущности, отправка, отображение ошибок.

Read more

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

Как настроить HttpMessageConverters для REST API с Spring и как использовать эти конвертеры с RestTemplate.

Read more

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-метод

Параметр HTTPmethod имеетno default, поэтому, если мы не укажем значение, оно будет отображаться на любой HTTP-запрос.

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

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

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

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

3. RequestMapping и заголовки 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

И даже несколько заголовков через атрибутheader@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 Потребляет и производит

Особого внимания заслуживает сопоставление методаmedia types produced by a controller - мы можем сопоставить запрос на основе его заголовкаAccept с помощью атрибута заголовков@RequestMapping, представленного выше:

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

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

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

Начиная с Spring 3.1,@RequestMapping annotation now has the produces and the consumes attributes специально для этой цели:

@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

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

@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.example.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.example.spring.web.controller
  .FooController.getFoosAsJsonFromBrowser()
mapped.

Последнее замечание о новом механизмеproduces иconsumes - они ведут себя иначе, чем большинство других аннотаций: если они указаны на уровне типа,the method level annotations do not complement but override - это информация уровня типа.

И, конечно, если вы хотите глубже изучить создание REST API с помощью Spring -check outthe new REST with Spring course.

4. RequestMapping с переменными пути

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

4.1. Одиночный@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

Если имя параметра метода точно совпадает с именем переменной пути, то это можно упростить с помощьюusing @PathVariable with no value:

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

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

@PathVariable long id

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

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

@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 с RegEx

Регулярные выражения также можно использовать при отображении@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;
}

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

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

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

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

5. RequestMapping с параметрами запроса

@RequestMapping позволяет легкоmapping of URL parameters with the @RequestParam annotation. __

Теперь мы отображаем запрос на 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 can optionally define the parameters - еще один способ сузить отображение запроса:

@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, так и параметрsecond.

6. RequestMapping Угловые ящики

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

Хотя для одного метода контроллера обычно используется одно значение пути@RequestMapping, это просто хорошая практика, а не жесткое и быстрое правило - в некоторых случаях может потребоваться сопоставление нескольких запросов с одним и тем же методом. В этом случаеthe value attribute of @RequestMapping does accept multiple mappings, а не только один:

@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.example.web.controller.FooMappingExamplesController.duplicateEx()
  to {[/ex/foos/duplicate],methods=[GET]}:
  There is already 'fooMappingExamplesController' bean method
  public java.lang.String org.example.web.controller.FooMappingExamplesController.duplicate() mapped.

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

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

@GetMapping(value = "foos/duplicate/xml", produces = MediaType.APPLICATION_XML_VALUE)
public String duplicateXml() {
    return "Duplicate";
}

@GetMapping(value = "foos/duplicate/json", produces = MediaType.APPLICATION_JSON_VALUE)
public String duplicateJson() {
    return "Duplicate";
}

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

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

Spring Framework 4.3 представил аннотации отображения HTTPa few new, все на основе@RequestMapping:

  • @GetMapping

  • @PostMapping

  • @PutMapping

  • @DeleteMapping

  • @PatchMapping

Эти новые аннотации могут улучшить читаемость и уменьшить детализацию кода. Давайте посмотрим на эти новые аннотации в действии, создав 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);
}

Подробнее об этом можно прочитать вhere.

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

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

package org.example.spring.web.controller;

@Controller
public class FooController { ... }

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

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

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

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

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

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