Spring MVC: Складний об'єкт як GET @RequestParam


192

Припустимо, у мене є сторінка, що містить перелік об’єктів на столі, і мені потрібно поставити форму для фільтрації таблиці. Фільтр надсилається як 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
Спробуйте використовувати "@ModelAttribute" у поєднанні з "@RequestMapping", де MyObject буде вашою моделлю.
michal

@michal +1. Ось кілька навчальних посібників, що показують, як це зробити: Весна 3 MVC: Обробка форм навесні 3.0 MVC , Що таке та як користуватися@ModelAttribute , Приклад обробки форми MVC Spring Spring . Просто перейдіть на Google " Весняна обробка форми MVC ", і ви отримаєте безліч навчальних посібників / прикладів. Але обов'язково використовуйте сучасний спосіб обробки форми, тобто Spring v2.5 +
informatik01

Відповіді:


249

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

public @ResponseBody List<MyObject> myAction(
    @RequestParam(value = "page", required = false) int page,
    MyObject myObject)

8
Біджу, ти можеш навести приклад, як це назвати? Я роблю основний http GET дзвінок в API відпочинку, і він не має фантазійних форм.
bschandramohan

33
Зауважте, що Spring за замовчуванням отримувачі / сетери для MyObject автоматично прив'язують її. В іншому випадку це не зв'яже мій об'єкт.
aka_sh

20
Як ви можете встановити поля необов’язкові / необов'язкові в MyObject? Не знаєте, як знайти належну документацію для цього ..
worldsayshi

6
@Biju, Чи є спосіб контролювати значення за замовчуванням і необхідні для MyObjectцього, аналогічним чином ми можемо це зробити з @RequestParam?
Олексій

7
@BijuKunjummen Як я можу оновити, MyObjectщоб прийняти параметри запиту у випадку Snake та відобразити його до члена справи верблюда MyObject. Наприклад, ?book_id=4чи слід зіставити з bookIdчленом MyObject?
Vivek Vardhan

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}]

Я сподіваюся, що це допомагає :)

ОНОВЛЕННЯ / КОТЛІН

Оскільки в даний час я багато працюю з Котліном, якщо хтось хоче визначити подібний DTO, клас у Котліні повинен мати таку форму:

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

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

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

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

пружина (перевірена у завантаженні) повертає наступну помилку для запиту, зазначеного у відповіді:

"Не вдалося перетворити значення типу 'java.lang.String []' до потрібного типу 'java.lang.Long []'; вкладеним винятком є ​​java.lang.NumberFormatException: Для рядка введення: \" 353,234 \ ""

Клас даних буде працювати лише для такої форми парами запиту:

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

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


1
чи можна встановити "потрібно" на поля dto?
Звичайна

4
Пропоную спробувати валідатори Spring MVC. Приклад: codejava.net/frameworks/spring/…
Nowak

Дуже цікаво, що для цього не потрібно анотації! Цікаво, чи є явна примітка до цієї, хоч і непотрібної?
Джеймс Уоткінс

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 параметрами, але насправді відображений метод має лише два параметри. Я також спробував рішення Biju, однак, що трапляється, що весна створює UserPreferences об'єкт із конструктором за замовчуванням і не заповнює дані.

Я вирішив проблему, надіславши рядок налаштувань JSon від клієнтської сторони та обробляючи її так, ніби це String на стороні сервера;

клієнт:

 $.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. На мій погляд, причина, чому весна не розпізнає надіслані дані, - це тип вмісту.


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?";
}

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

Прийнята відповідь працює як шарм, але якщо об’єкт має список об’єктів, він не працюватиме так, як очікувалося, ось ось моє рішення після деякого копання.

Дотримуючись цієї нитки , ось як я це зробив.

  • Frontend: об'єктивуйте об'єкт, ніж закодуйте його в base64 для подання.
  • Бекенд: декодуйте рядок 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
)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.