Spring MVC: сложный объект как GET @RequestParam

193

Предположим, у меня есть страница со списком объектов на таблице, и мне нужно поместить форму для фильтрации таблицы. Фильтр отправляется в виде Ajax GET на URL-адрес, подобный следующему: http://foo.com/system/controller/action?page=1&prop1=x&prop2=y&prop3=z

И вместо того, чтобы иметь много параметров на моем контроллере, как:

@RequestMapping(value = "/action")
public @ResponseBody List<MyObject> myAction(
    @RequestParam(value = "page", required = false) int page,
    @RequestParam(value = "prop1", required = false) String prop1,
    @RequestParam(value = "prop2", required = false) String prop2,
    @RequestParam(value = "prop3", required = false) String prop3) { ... }

И предположим, у меня есть MyObject как:

public class MyObject {
    private String prop1;
    private String prop2;
    private String prop3;

    //Getters and setters
    ...
}

Я хочу сделать что-то вроде:

@RequestMapping(value = "/action")
public @ResponseBody List<MyObject> myAction(
    @RequestParam(value = "page", required = false) int page,
    @RequestParam(value = "myObject", required = false) MyObject myObject,) { ... }

Является ли это возможным? Как я могу это сделать?

renanleandrof
источник
1
Попробуйте использовать @ModelAttribute в сочетании с @RequestMapping, где MyObject будет вашей моделью.
Михал
@michal +1. Вот пара учебных пособий, показывающих, как это сделать: Spring 3 MVC: Обработка форм в Spring 3.0 MVC , Что и как использовать@ModelAttribute , Пример обработки форм Spring MVC . Просто зайдите в Google « Обработка форм Spring MVC », и вы получите массу учебников / примеров. Но обязательно используйте современный способ обработки форм, то есть Spring v2.5 +
informatik01
Также полезно: Что есть @ModelAttributeв Spring MVC
informatik01

Ответы:

250

Вы можете сделать это абсолютно просто, просто удалите @RequestParamаннотацию, Spring аккуратно свяжет параметры вашего запроса с экземпляром вашего класса:

public @ResponseBody List<MyObject> myAction(
    @RequestParam(value = "page", required = false) int page,
    MyObject myObject)
Biju Kunjummen
источник
8
Биджу, можешь привести пример, как это назвать? Я делаю простой вызов HTTP GET в Rest API, и у него нет причудливых форм.
bschandramohan
33
Обратите внимание, что Spring по умолчанию запрашивает getters / setters для MyObject, чтобы связать его автоматически. В противном случае он не будет связывать myObject.
aka_sh
21
Как вы можете установить поля являются необязательными / необязательными в MyObject? Не уверен, как найти соответствующую документацию для этого ..
worldsayshi
6
@Biju, есть ли способ управления значениями по умолчанию и необходимыми для MyObjectэтого, аналогичным образом мы можем сделать с @RequestParam?
Алексей
7
@BijuKunjummen Как я могу обновить, MyObjectчтобы принимать параметры запроса в случае Снейка и сопоставить его с членом дела верблюда MyObject. Например, ?book_id=4должен быть сопоставлен с bookIdчленом MyObject?
Вивек
55

Я добавлю несколько коротких примеров от меня.

Класс DTO:

public class SearchDTO {
    private Long id[];

    public Long[] getId() {
        return id;
    }

    public void setId(Long[] id) {
        this.id = id;
    }
    // reflection toString from apache commons
    @Override
    public String toString() {
        return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }
}

Отображение запроса внутри класса контроллера:

@RequestMapping(value="/handle", method=RequestMethod.GET)
@ResponseBody
public String handleRequest(SearchDTO search) {
    LOG.info("criteria: {}", search);
    return "OK";
}

Запрос:

http://localhost:8080/app/handle?id=353,234

Результат:

[http-apr-8080-exec-7] INFO  c.g.g.r.f.w.ExampleController.handleRequest:59 - criteria: SearchDTO[id={353,234}]

Я надеюсь, что это помогает :)

ОБНОВЛЕНИЕ / КОТЛИН

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

class SearchDTO {
    var id: Array<Long>? = arrayOf()

    override fun toString(): String {
        // to string implementation
    }
}

С таким dataклассом:

data class SearchDTO(var id: Array<Long> = arrayOf())

Spring (протестирован в Boot) возвращает следующую ошибку для запроса, упомянутого в ответе:

"Не удалось преобразовать значение типа 'java.lang.String []' в требуемый тип 'java.lang.Long []'; вложенное исключение: java.lang.NumberFormatException: для входной строки: \" 353,234 \ ""

Класс данных будет работать только для следующей формы параметров запроса:

http://localhost:8080/handle?id=353&id=234

Будьте в курсе этого!

Пшемек Новак
источник
2
Можно ли установить «обязательные» для полей dto?
Нормально
4
Я предлагаю попробовать с валидаторами Spring MVC. Пример: codejava.net/frameworks/spring/…
Новак,
Очень любопытно, что это не требует аннотации! Интересно, есть ли явная аннотация для этого, хотя и ненужная?
Джеймс Уоткинс
8

У меня очень похожая проблема. На самом деле проблема глубже, как я думал. Я использую jquery, $.postкоторый использует Content-Type:application/x-www-form-urlencoded; charset=UTF-8по умолчанию. К сожалению, я основывал свою систему на этом, и когда мне был нужен сложный объект, @RequestParamя не мог просто заставить его произойти.

В моем случае я пытаюсь отправить пользовательские настройки с чем-то вроде;

 $.post("/updatePreferences",  
    {id: 'pr', preferences: p}, 
    function (response) {
 ...

На стороне клиента фактические необработанные данные отправляются на сервер;

...
id=pr&preferences%5BuserId%5D=1005012365&preferences%5Baudio%5D=false&preferences%5Btooltip%5D=true&preferences%5Blanguage%5D=en
...

разобрано как;

id:pr
preferences[userId]:1005012365
preferences[audio]:false
preferences[tooltip]:true
preferences[language]:en

и на стороне сервера есть;

@RequestMapping(value = "/updatePreferences")
public
@ResponseBody
Object updatePreferences(@RequestParam("id") String id, @RequestParam("preferences") UserPreferences preferences) {

    ...
        return someService.call(preferences);
    ...
}

Я попытался @ModelAttribute, добавил сеттер / геттеры, конструкторы со всеми возможностями, UserPreferencesно без шансов, поскольку он распознал отправленные данные как 5 параметров, но на самом деле отображенный метод имеет только 2 параметра. Я также попробовал решение Biju, однако в результате Spring создает объект UserPreferences с конструктором по умолчанию и не заполняет данные.

Я решил проблему, отправив строку предпочтений JSon со стороны клиента и обработал ее, как если бы это была строка на стороне сервера;

клиент:

 $.post("/updatePreferences",  
    {id: 'pr', preferences: JSON.stringify(p)}, 
    function (response) {
 ...

сервер:

@RequestMapping(value = "/updatePreferences")
public
@ResponseBody
Object updatePreferences(@RequestParam("id") String id, @RequestParam("preferences") String preferencesJSon) {


        String ret = null;
        ObjectMapper mapper = new ObjectMapper();
        try {
            UserPreferences userPreferences = mapper.readValue(preferencesJSon, UserPreferences.class);
            return someService.call(userPreferences);
        } catch (IOException e) {
            e.printStackTrace();
        }
}

вкратце, я сделал преобразование вручную внутри метода REST. По моему мнению, причина, по которой Spring не распознает отправленные данные, заключается в типе контента.

Hevi
источник
1
Я только что испытал ту же проблему и решил ее таким же образом. Кстати, вы нашли лучшее решение на сегодня?
Шей Элкаям,
1
У меня точно такая же проблема. Я сделал более чистый обходной путь, используя@RequestMapping(method = POST, path = "/settings/{projectId}") public void settings(@PathVariable String projectId, @RequestBody ProjectSettings settings)
Петр Рождский
4

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

public class ExampleDTO {
    @NotNull
    private String mandatoryParam;

    private String optionalParam;

    @DateTimeFormat(iso = ISO.DATE) //accept Dates only in YYYY-MM-DD
    @NotNull
    private LocalDate testDate;

    public String getMandatoryParam() {
        return mandatoryParam;
    }
    public void setMandatoryParam(String mandatoryParam) {
        this.mandatoryParam = mandatoryParam;
    }
    public String getOptionalParam() {
        return optionalParam;
    }
    public void setOptionalParam(String optionalParam) {
        this.optionalParam = optionalParam;
    }
    public LocalDate getTestDate() {
        return testDate;
    }
    public void setTestDate(LocalDate testDate) {
        this.testDate = testDate;
    }
}

@RequestMapping(value = "/test", method = RequestMethod.GET)
public String testComplexObject (@Valid ExampleDTO e){
    System.out.println(e.getMandatoryParam() + " " + e.getTestDate());
    return "Does this work?";
}
FluffyDestroyerOfCode
источник
0

В то время как ответы , которые относятся к @ModelAttribute, @RequestParam, @PathParamи любит справедливы, есть небольшой глюк я столкнулся. Результирующий параметр метода - это прокси, который Spring оборачивает вокруг вашего DTO. Таким образом, если вы попытаетесь использовать его в контексте, который требует вашего собственного пользовательского типа, вы можете получить неожиданные результаты.

Следующее не будет работать:

@GetMapping(produces = APPLICATION_JSON_VALUE)
public ResponseEntity<CustomDto> request(@ModelAttribute CustomDto dto) {
    return ResponseEntity.ok(dto);
}

В моем случае попытка использовать его в привязке Джексона привела к com.fasterxml.jackson.databind.exc.InvalidDefinitionException.

Вам нужно будет создать новый объект из dto.

ДКО
источник
0

Да, Вы можете сделать это простым способом. Смотрите ниже код линий.

URL - http: // localhost: 8080 / get / request / multiple / param / by / map? Name = 'abc' & id = '123'

 @GetMapping(path = "/get/request/header/by/map")
    public ResponseEntity<String> getRequestParamInMap(@RequestParam Map<String,String> map){
        // Do your business here 
        return new ResponseEntity<String>(map.toString(),HttpStatus.OK);
    } 
vkstream
источник
0

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

Следуя этой теме , вот как я это сделал.

  • Внешний интерфейс: зафиксируйте ваш объект, затем закодируйте его в base64 для отправки.
  • Backend: декодировать строку base64, а затем преобразовать строку json в нужный объект.

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

Исходный объект: {страница: 1, размер: 5, фильтры: [{поле: "id", значение: 1, сравнение: "EQ"}

Кодированный объект: eyJwYWdlIjoxLCJzaXplIjo1LCJmaWx0ZXJzIjpbeyJmaWVsZCI6ImlkUGFyZW50IiwiY29tcGFyaXNvbiI6Ik5VTEwifV19

@GetMapping
fun list(@RequestParam search: String?): ResponseEntity<ListDTO> {
    val filter: SearchFilterDTO = decodeSearchFieldDTO(search)
    ...
}

private fun decodeSearchFieldDTO(search: String?): SearchFilterDTO {
    if (search.isNullOrEmpty()) return SearchFilterDTO()
    return Gson().fromJson(String(Base64.getDecoder().decode(search)), SearchFilterDTO::class.java)
}

А вот SearchFilterDTO и FilterDTO

class SearchFilterDTO(
    var currentPage: Int = 1,
    var pageSize: Int = 10,
    var sort: Sort? = null,
    var column: String? = null,
    var filters: List<FilterDTO> = ArrayList<FilterDTO>(),
    var paged: Boolean = true
)

class FilterDTO(
    var field: String,
    var value: Any,
    var comparison: Comparison
)
Габриэль Брито
источник