web-dev-qa-db-fra.com

Spring MVC: objet complexe en tant que GET @RequestParam

Supposons que j'ai une page qui répertorie les objets sur une table et que je dois mettre un formulaire pour filtrer la table. Le filtre est envoyé en tant que Ajax GET à une URL du type de celle-ci: http://foo.com/system/controller/action?page=1&prop1=x&prop2=y&prop3=z

Et au lieu d'avoir beaucoup de paramètres sur mon contrôleur comme:

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

Et en supposant que j'ai MyObject comme:

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

    //Getters and setters
    ...
}

Je veux faire quelque chose comme:

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

C'est possible? Comment puis je faire ça?

139
renanleandrof

Vous pouvez absolument faire cela, il suffit de supprimer l'annotation @RequestParam, Spring reliera proprement vos paramètres de requête à votre instance de classe:

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

Je vais ajouter quelques exemples de ma part.

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

Demander un mappage à l'intérieur de la classe de contrôleur:

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

Question:

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

Résultat:

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

J'espère que ça aide :)

MISE À JOUR/KOTLIN

Parce que, actuellement, je travaille beaucoup avec Kotlin si quelqu'un veut définir un DTO similaire, la classe dans Kotlin devrait avoir la forme suivante:

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

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

Avec la classe data comme celle-ci:

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

le ressort (testé au démarrage) renvoie l'erreur suivante pour la demande mentionnée dans la réponse:

"Échec de la conversion de la valeur de type 'Java.lang.String []' en type requis 'Java.lang.Long []'; l'exception imbriquée est Java.lang.NumberFormatException: pour chaîne d'entrée: \" 353,234\""

La classe de données ne fonctionnera que pour le formulaire de paramètres de requête suivant:

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

Soyez conscient de cela!

38
Przemek Nowak

J'ai un problème très similaire. En fait, le problème est plus profond que je le pensais. J'utilise jquery $.post qui utilise Content-Type:application/x-www-form-urlencoded; charset=UTF-8 par défaut. Malheureusement, j'ai basé mon système sur cela et quand j'avais besoin d'un objet complexe en tant que @RequestParam, je ne pouvais pas le faire.

Dans mon cas, j'essaie d'envoyer les préférences de l'utilisateur avec quelque chose comme;

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

Du côté client, les données brutes envoyées au serveur sont: 

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

analysé comme;

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

et le côté serveur est;

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

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

J'ai essayé @ModelAttribute, ajouté des setter/getters, des constructeurs offrant toutes les possibilités à UserPreferences, mais aucune chance, car les données envoyées étaient reconnues comme étant 5 paramètres, mais en réalité, la méthode mappée ne comporte que 2 paramètres. J'ai aussi essayé la solution de Biju, cependant, spring crée un objet UserPreferences avec le constructeur par défaut et ne remplit pas les données.

J'ai résolu le problème en envoyant la chaîne JSon des préférences du côté client et en le traitant comme s'il s'agissait d'une chaîne du côté serveur;

client:

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

serveur:

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

pour résumer, j’ai fait la conversion manuellement dans la méthode REST. À mon avis, la raison pour laquelle spring ne reconnaît pas les données envoyées est le type de contenu.

7
hevi

Tandis que les réponses qui font référence à @ModelAttribute, @RequestParam, @PathParam et aux préférences sont valables, il y a un petit piège que j'ai rencontré. Le paramètre de méthode résultant est un proxy que Spring entoure votre DTO. Ainsi, si vous essayez de l'utiliser dans un contexte qui requiert votre propre type personnalisé, vous risquez d'obtenir des résultats inattendus.

Ce qui suit ne fonctionnera pas:

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

Dans mon cas, essayer de l'utiliser dans la liaison de Jackson a abouti à un com.fasterxml.jackson.databind.exc.InvalidDefinitionException.

Vous devrez créer un nouvel objet à partir du dto.

0
DKO

Puisque la question sur la façon de définir les champs obligatoires apparaît sous chaque message, j'ai écrit un petit exemple sur la façon de définir les champs comme requis:

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
user2664623