273 votes

Spring MVC : objet complexe comme GET @RequestParam

Supposons que j'ai une page qui liste les objets dans un tableau et que j'ai besoin de mettre un formulaire pour filtrer le tableau. Le filtre est envoyé comme un Ajax GET à une URL comme 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 supposons que j'ai MyObject en tant que :

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

Est-ce possible ? Comment puis-je le faire ?

366voto

Biju Kunjummen Points 20258

Vous pouvez tout à fait le faire, il suffit de retirer le @RequestParam Spring liera proprement les paramètres de votre requête à votre instance de classe :

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

70voto

przemek Points 161

Je vais ajouter quelques courts 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);
    }
}

Mappage des requêtes dans la classe du contrôleur :

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

Une requête :

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 cela vous aidera :)

MISE À JOUR / KOTLIN

Parce qu'actuellement je travaille beaucoup avec Kotlin si quelqu'un veut définir des DTO similaires la classe en Kotlin devrait avoir la forme suivante :

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

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

Avec le data classe comme celle-ci :

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

le Spring (testé dans Boot) renvoie l'erreur suivante pour la requête mentionnée dans la réponse :

"Failed to convert value of type 'java.lang.String[]' to required type java.lang.Long[]' ; l'exception imbriquée est la suivante java.lang.NumberFormatException : Pour la chaîne d'entrée : \"353,234\""

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

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

Soyez-en conscient !

12voto

Comme la question de savoir comment rendre les champs obligatoires revient dans chaque article, j'ai écrit un petit exemple sur la façon de rendre les champs obligatoires :

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

//Add this to your rest controller class
@RequestMapping(value = "/test", method = RequestMethod.GET)
public String testComplexObject (@Valid ExampleDTO e){
    System.out.println(e.getMandatoryParam() + " " + e.getTestDate());
    return "Does this work?";
}

7voto

hevi Points 523

J'ai un problème très similaire. En fait, le problème est plus profond que je ne 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 là-dessus et lorsque j'ai eu besoin d'un objet complexe en tant que @RequestParam Je ne pouvais pas faire en sorte que ça arrive.

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é du client, les données brutes envoyées au serveur sont les suivantes ;

...
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 l'est ;

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

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

J'ai essayé @ModelAttribute Nous avons ajouté des fixateurs, des récupérateurs et des constructeurs avec toutes les possibilités de l'utilisateur. UserPreferences mais aucune chance car il a reconnu les données envoyées comme 5 paramètres mais en fait la méthode mappée n'a que 2 paramètres. J'ai également essayé la solution de Biju, mais ce qui se passe, c'est que 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 la 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. A mon avis, la raison pour laquelle Spring ne reconnaît pas les données envoyées est le content-type.

1voto

DKO Points 1

Alors que les réponses qui font référence à @ModelAttribute , @RequestParam , @PathParam et les goûts sont valables, mais il y a un petit problème que j'ai rencontré. Le paramètre de méthode résultant est un proxy que Spring enroule autour de votre DTO. Donc, si vous essayez de l'utiliser dans un contexte qui nécessite votre propre type personnalisé, vous pouvez obtenir des résultats inattendus.

Les éléments suivants ne fonctionneront pas :

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

Dans mon cas, la tentative de l'utiliser dans la liaison Jackson a donné lieu à une com.fasterxml.jackson.databind.exc.InvalidDefinitionException .

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X