Spring MVC-почему нельзя использовать @RequestBody и @RequestParam вместе

использование клиента HTTP dev с почтовым запросом и приложением типа контента / x-www-form-urlencoded

1) Только @RequestBody

запрос-localhost: 8080 / SpringMVC / добро пожаловать В 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 / добро пожаловать В 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 / добро пожаловать В 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) выше с измененным положением params

запрос-localhost: 8080 / SpringMVC / добро пожаловать В 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 / добро пожаловать?имя=АБВ В 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";
}

/ / имя - "xyz", а тело - "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' тело пусто

может кто-нибудь объяснить такое поведение?

4 ответов


на @RequestBody У javadoc государств

Аннотация, указывающая параметр метода, должна быть привязана к телу веб-запроса.

он использует зарегистрированные экземпляры HttpMessageConverter десериализовать тело запроса в объект аннотированного типа параметра.

и @RequestParam

аннотация, которая указывает, что параметр метода должен быть привязан к веб-запрос параметр.

  1. Spring связывает тело запроса с параметром, аннотированным @RequestBody.

  2. Spring связывает параметры запроса из тела запроса (параметры в кодировке url) с параметром метода. Весной будет использовать имя параметра, т. е.. name, чтобы отобразить параметр.

  3. параметры разрешаются по порядку. The @RequestBody обрабатывается первым. Весна поглотит все HttpServletRequest InputStream. Когда он затем пытается решить @RequestParam, который по умолчанию required, в строке запроса нет параметра запроса или того, что осталось от тела запроса, т. е. ничего. Таким образом, он терпит неудачу С 400, потому что запрос не может быть правильно обработан методом обработчика.

  4. обработчик @RequestParam действует сначала, читая, что он может из HttpServletRequest InputStream сопоставить параметр запроса, т. е. вся строка запроса/url-кодированные параметры. Он делает так и получает значение abc сопоставляется с параметром name. Когда обработчик для @RequestBody выполняется, в теле запроса ничего не осталось, поэтому используемый аргумент-пустая строка.

  5. обработчик @RequestBody считывает тело и привязывает его к параметру. Обработчик @RequestParam затем можно получить параметр запроса из строки запроса URL.

  6. обработчик @RequestParam считывает как из тела, так и из строки запроса URL. Обычно он ставил их в Map, но так как параметр имеет тип String, Весна сериализует Map в качестве значения, разделенные запятыми. Обработчик @RequestBody тогда, опять же, нечего читать из тела.


Я знаю, что слишком поздно отвечать на этот вопрос, но все же это может помочь кому-то читателям. Кажется, проблемы с версией. Я запускаю все эти тесты с spring 4.1.4 и обнаружил, что порядок @RequestBody и @RequestParam не имеет значения.

  1. то же, что и ваш результат
  2. то же, что и ваш результат
  3. дал body= "name=abc" и name = "abc"
  4. же 3.
  5. body ="name=abc", name = "xyz,abc"
  6. то же, что и 5.

вы также можете просто изменить состояние @RequestParam default required на false, чтобы не генерировать код состояния HTTP-ответа 400. Это позволит вам разместить аннотации в любом порядке, который вам нравится.

@RequestParam(required = false)String name

это происходит из-за не очень прямой спецификации сервлета. Если вы работаете с родным HttpServletRequest реализация вы не можете получить как тело кодирования URL, так и параметры. Весна делает некоторые обходные пути, которые делают его еще более странным и непрозрачным.

в таких случаях Spring (версия 3.2.4) повторно отображает тело для вас, используя данные из getParameterMap() метод. Он смешивает параметры GET и POST и нарушает порядок параметров. Класс, который несет ответственность для хаоса ServletServerHttpRequest. К сожалению, его нельзя заменить, но класс StringHttpMessageConverter может быть.

чистое решение, к сожалению, не просто:

  1. замена StringHttpMessageConverter. Копировать / перезаписать исходный метод настройки класса readInternal().
  2. упаковка 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-запрос сервлета теряет параметры из тела POST после прочтения его один раз