Spring MVC: сложный объект как GET @RequestParam
Предположим, у меня есть страница, в которой перечислены объекты в таблице, и мне нужно поместить форму для фильтрации таблицы. Фильтр отправляется как 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,) { ... }
Возможно ли это?
Как я могу это сделать?
Ответы
Ответ 1
Вы можете это сделать, просто удалите аннотацию @RequestParam
, Spring будет чисто привязывать ваши параметры запроса к вашему экземпляру класса:
public @ResponseBody List<MyObject> myAction(
@RequestParam(value = "page", required = false) int page,
MyObject myObject)
Ответ 2
Я добавлю короткий пример от меня.
Класс 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
Помните об этом!
Ответ 3
У меня очень похожая проблема. На самом деле проблема глубже, как я думал. Я использую 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
, добавил setter/getters, конструкторы со всеми возможностями 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 не распознает отправленные данные, является типом контента.
Ответ 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?";
}
Ответ 5
Хотя ответы, относящиеся к @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.