web-dev-qa-db-fra.com

JAX-RS Publier plusieurs objets

J'ai une méthode;

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(ObjectOne objectOne, ObjectTwo objectTwo)

Maintenant, je sais que je peux publier un seul objet au format JSON, en le plaçant simplement dans le corps . Mais est-il possible de créer plusieurs objets? Si c'est le cas, comment?

65
Thys

La réponse est non

La raison est simple: cela concerne les paramètres que vous pouvez recevoir dans une méthode. Ils doivent être liés à la demande. Droite? Ils doivent donc être soit des en-têtes, soit des cookies, soit des paramètres de requête, des paramètres de matrice, des paramètres de chemin d'accès ou corps de la requête. (Juste pour raconter l'histoire complète, il existe d'autres types de paramètres appelés contexte). 

Désormais, lorsque vous recevez un objet JSON dans votre demande, vous le recevez dans un corps de requête . Combien d'organes la demande peut avoir? Seul et l'unique. Vous ne pouvez donc recevoir qu’un seul objet JSON.

57
Tarlog

Vous ne pouvez pas utiliser votre méthode comme celle-ci comme indiqué correctement par Tarlog.

Cependant, vous pouvez faire ceci:

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(List<ObjectOne> objects)

ou ca: 

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(BeanWithObjectOneAndObjectTwo containerObject)

De plus, vous pouvez toujours combiner votre méthode avec les paramètres GET:

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(List<ObjectOne> objects, @QueryParam("objectTwoId") long objectTwoId)
80
tine2k

Si nous regardons ce que le PO essaie de faire, il essaie de publier deux objets JSON (éventuellement non liés). Tout d'abord, toute solution consistant à essayer d'envoyer une partie en tant que corps et une partie en tant qu'autre paramètre, IMO, est une {horrible} solution. POST les données doivent aller dans le corps. Ce n'est pas juste de faire quelque chose juste parce que ça marche. Certaines solutions de rechange peuvent enfreindre les principes de base REST. 

Je vois quelques solutions

  1. Utilisez application/x-www-form-urlencoded
  2. Utiliser plusieurs parties
  3. Emballez-les simplement dans un seul objet parent

1. Utilisez application/x-www-form-urlencoded

Une autre option consiste simplement à utiliser application/x-www-form-urlencoded. Nous pouvons réellement avoir des valeurs JSON. Pour examiner

curl -v http://localhost:8080/api/model \
     -d 'one={"modelOne":"helloone"}' \
     -d 'two={"modelTwo":"hellotwo"}'

public class ModelOne {
    public String modelOne;
}

public class ModelTwo {
    public String modelTwo;
}

@Path("model")
public class ModelResource {

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public String post(@FormParam("one") ModelOne modelOne,
                       @FormParam("two") ModelTwo modelTwo) {
        return modelOne.modelOne + ":" + modelTwo.modelTwo;
    }
}

La seule chose dont nous avons besoin pour que cela fonctionne est une ParamConverterProvider pour que cela fonctionne. En voici une qui a été implémentée par Michal Gadjos de la Jersey Team (trouvé ici avec explication ).

@Provider
public class JacksonJsonParamConverterProvider implements ParamConverterProvider {

    @Context
    private Providers providers;

    @Override
    public <T> ParamConverter<T> getConverter(final Class<T> rawType,
                                              final Type genericType,
                                              final Annotation[] annotations) {
        // Check whether we can convert the given type with Jackson.
        final MessageBodyReader<T> mbr = providers.getMessageBodyReader(rawType,
                genericType, annotations, MediaType.APPLICATION_JSON_TYPE);
        if (mbr == null
              || !mbr.isReadable(rawType, genericType, annotations, MediaType.APPLICATION_JSON_TYPE)) {
            return null;
        }

        // Obtain custom ObjectMapper for special handling.
        final ContextResolver<ObjectMapper> contextResolver = providers
                .getContextResolver(ObjectMapper.class, MediaType.APPLICATION_JSON_TYPE);

        final ObjectMapper mapper = contextResolver != null ?
                contextResolver.getContext(rawType) : new ObjectMapper();

        // Create ParamConverter.
        return new ParamConverter<T>() {

            @Override
            public T fromString(final String value) {
                try {
                    return mapper.reader(rawType).readValue(value);
                } catch (IOException e) {
                    throw new ProcessingException(e);
                }
            }

            @Override
            public String toString(final T value) {
                try {
                    return mapper.writer().writeValueAsString(value);
                } catch (JsonProcessingException e) {
                    throw new ProcessingException(e);
                }
            }
        };
    }
}

Si vous ne analysez pas les ressources et les fournisseurs, enregistrez simplement ce fournisseur et l'exemple ci-dessus devrait fonctionner.

2. Utilisez plusieurs parties

Une solution que personne n'a mentionnée consiste à utiliser multipart . Cela nous permet d’envoyer des pièces arbitraires dans une requête. Étant donné que chaque demande ne peut avoir qu'un seul corps d'entité, le travail en contournement est une solution en plusieurs parties, car elle permet de faire figurer différentes parties (avec leurs propres types de contenu) dans le corps d'entité.

Voici un exemple utilisant Jersey (voir document officiel ici )

Dépendance

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-multipart</artifactId>
    <version>${jersey-2.x.version}</version>
</dependency>

Enregistrez la MultipartFeature

import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("/api")
public class JerseyApplication extends ResourceConfig {

    public JerseyApplication() {
        packages("stackoverflow.jersey");
        register(MultiPartFeature.class);
    }
}

Classe de ressources

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.media.multipart.FormDataParam;

@Path("foobar")
public class MultipartResource {

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public Response postFooBar(@FormDataParam("foo") Foo foo,
                               @FormDataParam("bar") Bar bar) {
        String response = foo.foo + "; " + bar.bar;
        return Response.ok(response).build();
    }

    public static class Foo {
        public String foo;
    }

    public static class Bar {
        public String bar;
    }
}

Le problème avec certains clients est qu’il n’existe pas de moyen de définir le Content-Type de chaque partie du corps, ce qui est nécessaire pour que ce qui précède fonctionne. Le fournisseur en plusieurs parties recherchera le lecteur de corps du message, en fonction du type de chaque partie. Si elle n'est pas définie sur application/json ou un type, les variables Foo ou Bar ont un lecteur, cela échouera. Nous allons utiliser JSON ici. Il n'y a pas de configuration supplémentaire, mais d'avoir un lecteur disponible. Je vais utiliser Jackson. Avec la dépendance ci-dessous, aucune autre configuration ne devrait être requise, car le fournisseur sera découvert via l'analyse de chemin d'accès aux classes.

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>${jersey-2.x.version}</version>
</dependency>

Maintenant le test. Je vais utiliser cURL . Vous pouvez voir que je règle explicitement le Content-Type pour chaque partie avec type. Le -F signifie une partie différente. (Voir tout au bas de l'article pour une idée de l'apparence réelle du corps de la requête.)

curl -v -X POST \ -H "Content-Type:multipart/form-data" \ -F "bar={\"bar\":\"BarBar\"};type=application/json" \ -F "foo={\"foo\":\"FooFoo\"};type=application/json" \ http://localhost:8080/api/foobar
Résultat:FooFoo; BarBar

Le résultat est exactement comme prévu. Si vous examinez la méthode de ressource, vous ne ferez que renvoyer cette chaîne foo.foo + "; " + bar.bar, collectée à partir des deux objets JSON.

Vous pouvez voir quelques exemples utilisant différents clients JAX-RS, dans les liens ci-dessous. Vous verrez également des exemples côté serveur provenant également de ces différentes implémentations de JAX-RS. Chaque lien devrait avoir quelque part un lien vers la documentation officielle de cette implémentation.

Il existe d'autres implémentations JAX-RS, mais vous devrez trouver la documentation vous-même. Les trois ci-dessus sont les seuls que j'ai l'expérience.

En ce qui concerne les clients Javascript, la plupart des exemples que je vois (par exemple, certains d'entre eux impliquent de définir le Content-Type sur undefined/false (avec FormData), laissant le navigateur le gérer. Mais cela ne fonctionnera pas pour nous, car Le navigateur ne définira pas le Content-Type pour chaque pièce. Le type par défaut est text/plain.

Je suis sûr que certaines bibliothèques permettent de définir le type de chaque partie, mais pour vous montrer comment faire manuellement, je posterai un exemple (j'ai un peu d'aide de ici . Je ' Vous utiliserez Angular, mais le travail fastidieux de construction du corps de l’entité sera du vieux Javascript.

<!DOCTYPE html>
<html ng-app="multipartApp">
    <head>
        <script src="js/libs/angular.js/angular.js"></script>
        <script>
            angular.module("multipartApp", [])
            .controller("defaultCtrl", function($scope, $http) {

                $scope.sendData = function() {
                    var foo = JSON.stringify({foo: "FooFoo"});
                    var bar = JSON.stringify({bar: "BarBar"});

                    var boundary = Math.random().toString().substr(2);                    
                    var header = "multipart/form-data; charset=utf-8; boundary=" + boundary;

                    $http({
                        url: "/api/foobar",
                        headers: { "Content-Type": header }, 
                        data: createRequest(foo, bar, boundary),
                        method: "POST"
                    }).then(function(response) {
                        $scope.result = response.data;
                    });
                };

                function createRequest(foo, bar, boundary) {
                    var multipart = "";
                    multipart += "--" + boundary
                        + "\r\nContent-Disposition: form-data; name=foo"
                        + "\r\nContent-type: application/json"
                        + "\r\n\r\n" + foo + "\r\n";        
                    multipart += "--" + boundary
                        + "\r\nContent-Disposition: form-data; name=bar"
                        + "\r\nContent-type: application/json"
                        + "\r\n\r\n" + bar + "\r\n";
                    multipart += "--" + boundary + "--\r\n";
                    return multipart;
                }
            });
        </script>
    </head>
    <body>
        <div ng-controller="defaultCtrl">
            <button ng-click="sendData()">Send</button>
            <p>{{result}}</p>
        </div>
    </body>
</html>

La partie intéressante est la fonction createRequest. C'est ici que nous construisons la multipartie, en définissant le Content-Type de chaque partie sur application/json et en concaténant les objets stringifiés foo et bar avec chaque partie. Si vous ne connaissez pas le format multipart/ voir ici pour plus d’informations . L'autre partie intéressante est l'en-tête. Nous l'avons mis à multipart/form-data

Voici le résultat. Dans Angular, je viens d'utiliser le résultat à afficher dans le code HTML, avec $scope.result = response.data. Le bouton que vous voyez était juste pour faire la demande. Vous verrez également les données de la demande dans firebug

enter image description here

3. Emballez-les simplement dans un seul objet parent

Cette option devrait être explicite, comme d'autres l'ont déjà mentionné.

31
Paul Samsotha

L’approche suivante est généralement appliquée dans ce type de cas:

TransferObject {
    ObjectOne objectOne;
    ObjectTwo objectTwo;

    //getters/setters
}

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(TransferObject object){
//        object.getObejctOne()....
}
6

Vous ne pouvez pas mettre deux objets distincts dans un seul appel POST comme expliqué par Tarlog.

Quoi qu'il en soit, vous pouvez créer un troisième objet conteneur contenant les deux premiers objets et le transmettre à l’appel du point de vente.

4
Giorgio

J'ai également fait face à ces problèmes. Cela aidera peut-être.

@POST
@Path("/{par}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Object centralService(@PathParam("par") String operation, Object requestEntity) throws JSONException {

    ObjectMapper objectMapper=new ObjectMapper();

    Cars cars = new Cars();  
    Seller seller = new Seller();
    String someThingElse;

    HashMap<String, Object> mapper = new HashMap<>(); //Diamond )))

    mapper = (HashMap<String, Object>) requestEntity;

    cars=objectMapper.convertValue(mapper.get("cars"), Cars.class);
    seller=objectMapper.convertValue(mapper.get("seller"), Seller.class);
    someThingElse=objectMapper.convertValue(mapper.get("someThingElse"), String.class);

    System.out.println("Cars Data "+cars.toString());

    System.out.println("Sellers Data "+seller.toString());

    System.out.println("SomeThingElse "+someThingElse);

    if (operation.equals("search")) {
        System.out.println("Searching");
    } else if (operation.equals("insertNewData")) {
        System.out.println("Inserting New Data");
    } else if (operation.equals("buyCar")) {
        System.out.println("Buying new Car");
    }

    JSONObject json=new JSONObject();
    json.put("result","Works Fine!!!");


    return json.toString();

}


*******CARS POJO********@XmlRootElement for Mapping Object to XML or JSON***

@XmlRootElement
public class Cars {
    private int id;
    private String brand;
    private String model;
    private String body_type;
    private String fuel;
    private String engine_volume;
    private String horsepower;
    private String transmission;
    private String drive;
    private String status;
    private String mileage;
    private String price;
    private String description;
    private String picture;
    private String fk_seller_oid;
    } // Setters and Getters Omitted 

*******SELLER POJO********@XmlRootElement for Mapping Object to XML or JSON***

@XmlRootElement
public class Seller {
    private int id;
    private String name;
    private String surname;
    private String phone;
    private String email;
    private String country;
    private String city;
    private String paste_date;
    }//Setters and Getters omitted too


*********************FRONT END Looks Like This******************

$(function(){
$('#post').on('click',function(){
        console.log('Begins');
        $.ajax({
            type:'POST',
            url: '/ENGINE/cars/test',
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            data:complexObject(),
            success: function(data){
                console.log('Sended and returned'+JSON.stringify(data));
            },
            error: function(err){
                console.log('Error');
                console.log("AJAX error in request: " + JSON.stringify(err, null, 2));
            }
        }); //-- END of Ajax
        console.log('Ends POST');
        console.log(formToJSON());

    }); // -- END of click function   POST


function complexObject(){
    return JSON.stringify({
                "cars":{"id":"1234","brand":"Mercedes","model":"S class","body_type":"Sedan","fuel":"Benzoline","engine_volume":"6.5",
                "horsepower":"1600","transmission":"Automat","drive":"Full PLag","status":"new","mileage":"7.00","price":"15000",
                "description":"new car and very Nice car","picture":"mercedes.jpg","fk_seller_oid":"1234444"},
        "seller":{  "id":"234","name":"Djonotan","surname":"Klinton","phone":"+994707702747","email":"[email protected]",                 "country":"Azeribaijan","city":"Baku","paste_date":"20150327"},
        "someThingElse":"String type of element"        
    }); 
} //-- END of Complex Object
});// -- END of JQuery -  Ajax
1
Musa

Remplacez @Consumes (MediaType.APPLICATION_JSON) Par @Consumes ({MediaType.APPLICATION_FORM_URLENCODED}) Vous pouvez ensuite insérer plusieurs objets dans le corps.

0
alexBai

Cela peut être fait en déclarant que la méthode POST accepte un tableau d'objets. Exemple comme ça

T[] create(@RequestBody T[] objects) {
for( T object : objects ) {
   service.create(object);
  }
}
0
Yoga Gowda