Spring MVC: Komplexes Objekt als GET @RequestParam

191

Angenommen, ich habe eine Seite, auf der die Objekte in einer Tabelle aufgelistet sind, und ich muss ein Formular einfügen, um die Tabelle zu filtern. Der Filter wird als Ajax GET an eine folgende URL gesendet: http://foo.com/system/controller/action?page=1&prop1=x&prop2=y&prop3=z

Und anstatt viele Parameter auf meinem Controller zu haben, wie:

@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) { ... }

Und angenommen, ich habe MyObject als:

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

    //Getters and setters
    ...
}

Ich möchte so etwas tun wie:

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

Ist es möglich? Wie kann ich das machen?

Renanleandrof
quelle
1
Versuchen Sie, '@ModelAttribute' in Kombination mit '@RequestMapping' zu verwenden, wobei MyObject Ihr Modell sein wird.
Michael
@michal +1. Hier ist ein paar Tutorials zeigen , wie das zu tun: Frühling 3 MVC: Handhabung Forms in Spring 3.0 MVC , Was ist und wie man Gebrauch@ModelAttribute , Spring MVC - Formular Beispiel Handhabung . Google einfach " Spring MVC Form Handling " und du bekommst eine Menge Tutorials / Beispiele. Verwenden Sie jedoch unbedingt die moderne Art der Formularbearbeitung, z. B. Spring v2.5 +
informatik01,
Auch nützlich: Was ist @ModelAttributeim Frühjahr MVC
informatik01

Antworten:

247

Sie können dies @RequestParamauf jeden Fall tun. Entfernen Sie einfach die Anmerkung. Spring bindet Ihre Anforderungsparameter sauber an Ihre Klasseninstanz:

public @ResponseBody List<MyObject> myAction(
    @RequestParam(value = "page", required = false) int page,
    MyObject myObject)
Biju Kunjummen
quelle
8
Biju, kannst du ein Beispiel geben, wie man das nennt? Ich mache einen einfachen http GET-Aufruf an die Rest-API und sie hat keine ausgefallenen Formen.
Bschandramohan
32
Beachten Sie, dass Spring standardmäßig Getter / Setter für das MyObject benötigt, um es automatisch zu binden. Andernfalls wird das myObject nicht gebunden.
aka_sh
20
Wie können Sie festlegen, dass Felder in MyObject optional / nicht optional sind? Ich
bin
6
@Biju, Gibt es eine Möglichkeit, die Standardwerte zu steuern, die für diese erforderlich sind MyObject, auf ähnliche Weise wie bei @RequestParam?
Alexey
7
@BijuKunjummen Wie kann ich die Aktualisierung MyObjectAbfrageparameter in Snake Fall zu übernehmen und es zu einem Kamel Fall Mitglied der Karte MyObject. Sollte zum Beispiel ?book_id=4mit bookIdMitglied des MyObject?
Vivek Vardhan
55

Ich werde ein kurzes Beispiel von mir hinzufügen.

Die DTO-Klasse:

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);
    }
}

Anforderungszuordnung innerhalb der Controller-Klasse:

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

Abfrage:

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

Ergebnis:

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

Ich hoffe, es hilft :)

UPDATE / KOTLIN

Da ich derzeit viel mit Kotlin zusammenarbeite, sollte die Klasse in Kotlin die folgende Form haben, wenn jemand ein ähnliches DTO definieren möchte:

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

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

Mit der dataKlasse wie dieser:

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

Die Feder (im Boot getestet) gibt den folgenden Fehler für die in der Antwort erwähnte Anforderung zurück:

"Fehler beim Konvertieren des Werts vom Typ 'java.lang.String []' in den erforderlichen Typ 'java.lang.Long []'; verschachtelte Ausnahme ist java.lang.NumberFormatException: Für Eingabezeichenfolge:" 353,234 "

Die Datenklasse funktioniert nur für das folgende Anforderungsparameterformular:

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

Sei dir dessen bewusst!

Przemek Nowak
quelle
1
ist es möglich, "erforderlich" auf dto-Felder zu setzen?
Normal
4
Ich schlage vor, es mit Spring MVC-Validatoren zu versuchen. Beispiel: codejava.net/frameworks/spring/…
Przemek Nowak
Es ist sehr merkwürdig, dass hierfür keine Anmerkung erforderlich ist! Ich frage mich, gibt es eine explizite Anmerkung dafür, wenn auch unnötig?
James Watkins
8

Ich habe ein sehr ähnliches Problem. Eigentlich ist das Problem tiefer als ich dachte. Ich verwende jquery, $.postdas Content-Type:application/x-www-form-urlencoded; charset=UTF-8standardmäßig verwendet wird. Leider habe ich mein System darauf aufgebaut und als ich ein komplexes Objekt als brauchte@RequestParam konnte ich es nicht einfach umsetzen.

In meinem Fall versuche ich, Benutzereinstellungen mit etwas wie zu senden;

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

Auf der Clientseite sind die tatsächlichen Rohdaten, die an den Server gesendet werden;

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

analysiert als;

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

und die Serverseite ist;

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

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

Ich habe es versucht @ModelAttribute, Setter / Getter hinzugefügt, Konstruktoren mit allen Möglichkeiten dazuUserPreferences aber ohne Chance da es die gesendeten Daten als 5 Parameter erkannte, aber tatsächlich hat die zugeordnete Methode nur 2 Parameter. Ich habe auch Bijus Lösung ausprobiert. Spring erstellt jedoch ein UserPreferences-Objekt mit dem Standardkonstruktor und füllt die Daten nicht aus.

Ich habe das Problem gelöst, indem ich eine JSon-Zeichenfolge mit den Einstellungen von der Clientseite gesendet und so behandelt habe, als wäre es eine Zeichenfolge auf der Serverseite.

Klient:

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

Server:

@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();
        }
}

Kurz gesagt, ich habe die Konvertierung manuell innerhalb der REST-Methode durchgeführt. Meiner Meinung nach ist der Grund, warum Spring die gesendeten Daten nicht erkennt, der Inhaltstyp.

hevi
quelle
1
Ich habe gerade das gleiche Problem erlebt und es auf die gleiche Weise gelöst. Haben Sie übrigens bis heute eine bessere Lösung gefunden?
Shay Elkayam
1
Ich habe auch genau das gleiche Problem. Ich habe eine sauberere Problemumgehung mit@RequestMapping(method = POST, path = "/settings/{projectId}") public void settings(@PathVariable String projectId, @RequestBody ProjectSettings settings)
Petr Újezdský
4

Da die Frage zum Festlegen von obligatorischen Feldern unter jedem Beitrag auftaucht, habe ich ein kleines Beispiel zum Festlegen von Feldern nach Bedarf geschrieben:

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
quelle
0

Während Antworten , die Bezug zu nehmen @ModelAttribute, @RequestParam, @PathParamund Ähnliches gültig sind, gibt es eine kleine Gotcha ich in lief. Der resultierende Methodenparameter ist ein Proxy, den Spring um Ihr DTO legt. Wenn Sie also versuchen, es in einem Kontext zu verwenden, der Ihren eigenen benutzerdefinierten Typ erfordert, erhalten Sie möglicherweise einige unerwartete Ergebnisse.

Folgendes wird nicht funktionieren:

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

In meinem Fall führte der Versuch, es in der Jackson-Bindung zu verwenden, zu a com.fasterxml.jackson.databind.exc.InvalidDefinitionException.

Sie müssen ein neues Objekt aus dem dto erstellen.

DKO
quelle
0

Ja, Sie können es auf einfache Weise tun. Siehe unten Code der Zeilen.

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
quelle
0

Akzeptierte Antworten funktionieren wie ein Zauber, aber wenn das Objekt eine Liste von Objekten enthält, funktioniert es nicht wie erwartet. Hier ist meine Lösung nach einigem Graben.

Nach diesem Thread- Ratschlag habe ich Folgendes getan.

  • Frontend: Stringifizieren Sie Ihr Objekt und codieren Sie es dann zur Übermittlung in base64.
  • Backend: Decodieren Sie den Base64-String und konvertieren Sie den String json in das gewünschte Objekt.

Es ist nicht das Beste zum Debuggen Ihrer API mit Postman, aber es funktioniert wie erwartet für mich.

Originalobjekt: {Seite: 1, Größe: 5, Filter: [{Feld: "id", Wert: 1, Vergleich: "EQ"}

Codiertes Objekt: 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)
}

Und hier die SearchFilterDTO und 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
)
Gabriel Brito
quelle