web-dev-qa-db-fra.com

REST avec la liaison de données complète Spring et Jackson

J'utilise Spring MVC pour gérer les demandes JSON POST. Sous les couvertures, j'utilise le MappingJacksonHttpMessageConverter construit sur le processeur Jackson JSON et activé lorsque vous utilisez le mvc: piloté par annotation.

Un de mes services reçoit une liste d'actions:

@RequestMapping(value="/executeActions", method=RequestMethod.POST)
    public @ResponseBody String executeActions(@RequestBody List<ActionImpl> actions) {
        logger.info("executeActions");
        return "ACK";
    }

J'ai trouvé que Jackson mappe le requestBody à une liste d'éléments Java.util.LinkedHashMap (simple liaison de données). Au lieu de cela, je voudrais que la demande soit liée à une liste d'objets typés (dans ce cas "ActionImpl").

Je sais que c'est facile à faire si vous utilisez directement l'ObjectMapper de Jackson:

List<ActionImpl> result = mapper.readValue(src, new TypeReference<List<ActionImpl>>() { }); 

mais je me demandais quel était le meilleur moyen d'y parvenir lors de l'utilisation de Spring MVC et de MappingJacksonHttpMessageConverter. Des indices?

Merci

40
Javier Ferrero

Je soupçonne que le problème est dû à l'effacement du type, c'est-à-dire qu'au lieu de passer le type de paramètre générique, peut-être que seules actions.getClass () est passé; et cela donnerait un type équivalent à List <?>.

Si cela est vrai, une possibilité serait d'utiliser une sous-classe intermédiaire, comme:

public class ActionImplList extends ArrayList<ActionImpl> { }

car cela conservera les informations de type même si seule la classe est passée. Donc alors:

public @ResponseBody String executeActions(@RequestBody ActionImplList actions)

ferait l'affaire. Pas optimal mais devrait fonctionner.

J'espère que quelqu'un avec plus de connaissances sur Spring MVC pourra nous expliquer pourquoi le type de paramètre n'est pas transmis (c'est peut-être un bug?), Mais au moins il y a un travail.

28
StaxMan

J'ai constaté que vous pouvez également contourner le problème d'effacement de type en utilisant un tableau en tant que @RequestBody au lieu d'une collection. Par exemple, ce qui suit fonctionnerait:

public @ResponseBody String executeActions(@RequestBody ActionImpl[] actions) { //... }
43
Michiel Verkaik

Pour votre information, la fonctionnalité sera disponible au printemps 3.2 (voir https://jira.springsource.org/browse/SPR-957 )

Je viens de le tester sur le M2 actuel et cela fonctionne comme un charme prêt à l'emploi (pas besoin de fournir d'annotation supplémentaire pour fournir le type paramétré, il sera automatiquement résolu par le nouveau MessageConverter)

9
Frédéric

Cette question est déjà ancienne, mais je pense que je peux quand même y contribuer un peu.

Comme l'a souligné StaxMan, cela est dû à l'effacement du type. Il est certain que devrait être possible, car vous pouvez obtenir les arguments génériques via réflexion à partir de la définition de la méthode. Cependant, le problème est l'API du HttpMessageConverter :

T read(Class<? extends T> clazz, HttpInputMessage inputMessage);

Ici, seul List.class sera transmis à la méthode. Ainsi, comme vous pouvez le voir, il est impossible d'implémenter un HttpMessageConverter qui calcule le type réel en regardant le type de paramètre de méthode, car ce n'est pas disponible.

Néanmoins, il est possible de coder votre propre solution de contournement - vous n'utiliserez simplement pas HttpMessageConverter. Spring MVC vous permet d'écrire votre propre WebArgumentResolver qui intervient avant les méthodes de résolution standard. Vous pouvez par exemple utiliser votre propre annotation personnalisée (@JsonRequestBody?) Qui utilise directement un ObjectMapper pour analyser votre valeur. Vous pourrez fournir le type de paramètre à partir de la méthode:

final Type parameterType= method.getParameterTypes()[index];
List<ActionImpl> result = mapper.readValue(src, new TypeReference<Object>>() {
    @Override
    public Type getType() {
        return parameterType;
    }
});

Pas vraiment de la façon dont TypeReference devait être utilisé, je suppose, mais ObjectMapper ne fournit pas de méthode plus appropriée.

2
waxwing

Avez-vous essayé de déclarer la méthode comme:

executeActions(@RequestBody TypeReference<List<ActionImpl>> actions)

Je ne l'ai pas essayé, mais sur la base de votre question, c'est la première chose que j'voudrais essayer.

0
skaffman