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

Spring RequestMapping

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

Упражнение: Сложение двух чисел

Даны два неотрицательный целых числа в виде непустых связных списков. Их цифры хранятся в обратном порядке. И каждый елемент списка содержить ровно одну цифру. Сложите эти два числа и верните сумму в виде связного списка ...

ANDROMEDA

1. Обзор

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

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

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 не имеет значения по умолчанию. Итак, если мы не укажем значение, оно будет сопоставлено с любым 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 С атрибутом заголовков

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

@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, представленного выше: ``

@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 теперь имеет атрибуты « производит » и « использует » специально для этой цели:

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

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

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

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

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

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

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

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

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

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

@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 с несколькими значениями :

@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 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;
}

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

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

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

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

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

@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;
}

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

@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

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

6. Угловые случаи RequestMapping

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

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

В этом случае атрибут value @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.foreach.web.controller.FooMappingExamplesController.duplicateEx()
to {[/ex/foos/duplicate],methods=[GET]}:
There is already 'fooMappingExamplesController' bean method
public java.lang.String org.foreach.web.controller.FooMappingExamplesController.duplicate() mapped.

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

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

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

@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
  • @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);
}

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

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

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

package org.foreach.spring.web.controller;

@Controller
public class FooController { ... }

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

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

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

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

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

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