Spring MVC - Почему бы не использовать @RequestBody и @RequestParam вместе
Использование HTTP-клиента-разработчика с почтовым запросом и приложением Content-Type/x-www-form-urlencoded
1) Только @RequestBody
Запрос - localhost: 8080/SpringMVC/welcome
В Body - name = abc
код -
@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestBody String body, Model model) {
model.addAttribute("message", body);
return "hello";
}
//Дает тело как 'name = abc', как ожидалось
2) Только @RequestParam
Запрос - localhost: 8080/SpringMVC/welcome
В Body - name = abc
код -
@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestParam String name, Model model) {
model.addAttribute("name", name);
return "hello";
}
//Дает имя как 'abc', как ожидалось
3) Оба вместе
Запрос - localhost: 8080/SpringMVC/welcome
В Body - name = abc
код -
@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestBody String body, @RequestParam String name, Model model) {
model.addAttribute("name", name);
model.addAttribute("message", body);
return "hello";
}
//Код ошибки HTTP 400 - запрос, отправленный клиентом, был синтаксически неправильным.
4) Выше с измененной позицией изменения
Запрос - localhost: 8080/SpringMVC/welcome
В Body - name = abc
код -
@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestParam String name, @RequestBody String body, Model model) {
model.addAttribute("name", name);
model.addAttribute("message", body);
return "hello";
}
//Ошибка. Имя - 'abc'. тело пустое
5) Вместе, но получите параметры url типа
Запрос - localhost: 8080/SpringMVC/welcome? name = xyz
В Body - name = abc
код -
@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestBody String body, @RequestParam String name, Model model) {
model.addAttribute("name", name);
model.addAttribute("message", body);
return "hello";
}
//name is 'xyz', а body - 'name = abc'
6) То же, что и 5), но с измененной позицией параметров
Код -
@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestParam String name, @RequestBody String body, Model model) {
model.addAttribute("name", name);
model.addAttribute("message", body);
return "hello";
}
//name = 'xyz, abc' body пуст
Может ли кто-нибудь объяснить это поведение?
Ответы
Ответ 1
Состояние @RequestBody
javadoc
Аннотации, указывающие параметр метода, должны быть привязаны к телу веб-запроса.
Он использует зарегистрированные экземпляры HttpMessageConverter
для десериализации тела запроса в объект типа аннотированных параметров.
И @RequestParam
Аннотации, указывающие, что параметр метода должен быть связан с параметр веб-запроса.
-
Spring связывает тело запроса с параметром, аннотированным с помощью @RequestBody
.
-
Spring связывает параметры запроса с телом запроса (параметрами, закодированными по URL), с параметром метода. Spring будет использовать имя параметра, т.е. name
, чтобы отобразить параметр.
-
Параметры разрешаются по порядку. Сначала обрабатывается @RequestBody
. Spring будет потреблять все HttpServletRequest
InputStream
. Затем, когда он пытается разрешить @RequestParam
, который по умолчанию required
, в строке запроса отсутствует параметр запроса или то, что осталось от тела запроса, т.е. ничего. Таким образом, он терпит неудачу с 400, потому что запрос не может быть правильно обработан методом обработчика.
-
Обработчик для @RequestParam
действует сначала, читая, что может сделать HttpServletRequest
InputStream
для сопоставления параметра запроса, т.е. все параметры запроса /url -encoded. Он делает это и получает значение abc
, отображаемое параметру name
. Когда выполняется обработчик для @RequestBody
, в теле запроса ничего не осталось, поэтому используемым аргументом является пустая строка.
-
Обработчик для @RequestBody
считывает тело и связывает его с параметром. Обработчик для @RequestParam
может затем получить параметр запроса из строки запроса URL.
-
Обработчик для @RequestParam
считывает как тело, так и строку запроса URL. Обычно они помещают их в Map
, но поскольку параметр имеет тип String
, Spring будет сериализовать Map
как значения, разделенные запятой. Обработчик для @RequestBody
, то, опять же, не осталось ничего читать с тела.
Ответ 2
Это происходит из-за не очень прямой спецификации сервлета. Если вы работаете с встроенной реализацией HttpServletRequest
, вы не можете получить тело кодировки URL-адреса и параметры. Spring делает некоторые обходные пути, которые делают его еще более странным и непрозрачным.
В таких случаях Spring (версия 3.2.4) повторно отображает тело для вас, используя данные из метода getParameterMap()
. Он смешивает параметры GET и POST и нарушает порядок параметров. Класс, который отвечает за хаос, ServletServerHttpRequest
. К сожалению, его нельзя заменить, но класс StringHttpMessageConverter
может быть.
Чистое решение, к сожалению, не так просто:
- Замена
StringHttpMessageConverter
. Копировать/Перезаписать исходный метод настройки класса readInternal()
.
- Обтекание
HttpServletRequest
перезаписи getInputStream()
, getReader()
и getParameter*()
методов.
В методе StringHttpMessageConverter # readInternal следующий код должен использоваться:
if (inputMessage instanceof ServletServerHttpRequest) {
ServletServerHttpRequest oo = (ServletServerHttpRequest)inputMessage;
input = oo.getServletRequest().getInputStream();
} else {
input = inputMessage.getBody();
}
Затем конвертер должен быть зарегистрирован в контексте.
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true/false">
<bean class="my-new-converter-class"/>
</mvc:message-converters>
</mvc:annotation-driven>
Ниже описан второй шаг: Запрос Http Servlet теряет параметры из тела POST после его чтения
Ответ 3
Возможно, слишком поздно ответить на этот вопрос, но я просто ответил, чтобы он помог другим читателям.
Это проблемы с версией. Я запускаю все эти тесты с помощью spring 4.1.4 и обнаружил, что порядок @RequestBody
и @RequestParam
не имеет значения.
- то же, что и ваш результат
- то же, что и ваш результат
- дал
body= "name=abc"
и name = "abc"
- То же, что и 3.
-
body ="name=abc"
, name = "xyz,abc"
- то же, что и 5.
Ответ 4
Вы также можете просто изменить статус по умолчанию, заданный по умолчанию @RequestParam, на false, чтобы код статуса ответа HTTP не генерировался. Это позволит вам размещать аннотации в любом порядке, который вам нравится.
@RequestParam(required = false)String name