wake-up-neo.net

JAX-RS Mehrere Objekte buchen

Ich habe eine Methode.

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

Jetzt weiß ich, dass ich ein einzelnes Objekt im Json-Format veröffentlichen kann, indem Sie es einfach in den Körper einfügen. Ist es jedoch möglich, mehrere Objekte zu erstellen? Wenn das so ist, wie?

65
Thys

Die Antwort lautet nein

Der Grund ist einfach: Hier geht es um die Parameter, die Sie in einer Methode erhalten können. Sie müssen sich auf die Anfrage beziehen. Recht? Sie müssen also entweder Header oder Cookies oder Abfrageparameter oder Matrixparameter oder Pfadparameter oder Request Body sein. (Um die komplette Geschichte zu erzählen, gibt es zusätzliche Typen von Parametern, die als Kontext bezeichnet werden). 

Wenn Sie nun in Ihrer Anfrage ein JSON-Objekt erhalten, erhalten Sie es in einem request body. Wie viele Stellen kann die Anfrage haben? Der eine und einzige. Sie können also nur ein JSON-Objekt empfangen.

57
Tarlog

Sie können nicht verwenden Ihre Methode wie von Tarlog richtig angegeben.

Sie können dies jedoch tun:

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

oder dieses: 

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

Darüber hinaus können Sie Ihre Methode immer mit GET-Parametern kombinieren:

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

Wenn wir uns anschauen, was das OP versucht, versucht er/sie, zwei (möglicherweise nicht zusammenhängende) JSON-Objekte zu posten. Zuerst ist jede Lösung, die versucht, einen Teil als Körper auszusenden, und ein Teil als anderer Parameter, IMO, schreckliche Lösungen. POST Daten sollten in den Körper gehen. Es ist nicht richtig, etwas zu tun, nur weil es funktioniert. Einige Problemumgehungen verstoßen möglicherweise gegen grundlegende REST - Prinzipien. 

Ich sehe einige Lösungen

  1. Verwenden Sie application/x-www-form-urlencoded
  2. Verwenden Sie Multipart
  3. Wickeln Sie sie einfach in ein einzelnes übergeordnetes Objekt

1. Verwenden Sie application/x-www-form-urlencoded

Eine andere Möglichkeit ist, einfach application/x-www-form-urlencoded zu verwenden. Wir können tatsächlich JSON-Werte haben. Zum Beispiel

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

Die einzige Sache, die wir benötigen, um dies zu erreichen, ist eine ParamConverterProvider, damit dies funktioniert. Unten ist eine, die von Michal Gadjos vom Jersey-Team implementiert wurde (gefunden hier mit Erklärung ).

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

Wenn Sie nicht nach Ressourcen und Providern suchen, registrieren Sie einfach diesen Provider. Das obige Beispiel sollte funktionieren.

2. Verwenden Sie Multipart

Eine Lösung, die niemand erwähnt hat, ist die Verwendung von multipart . Dies ermöglicht uns das Senden beliebiger Teile in einer Anfrage. Da jede Anforderung nur einen Entitätskörper haben kann, ist Multipart die Aufgabe, da unterschiedliche Teile (mit ihren eigenen Inhaltstypen) Teil des Entitätskörpers sein können.

Hier ist ein Beispiel mit Jersey (siehe offizielles Dokument hier )

Abhängigkeit

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

Registrieren Sie die 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);
    }
}

Ressourcenklasse

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

Bei einigen Clients besteht der knifflige Teil jedoch darin, dass es keinen Weg gibt, den Content-Type jedes Körperteils einzustellen, der für das Funktionieren des obigen Elements erforderlich ist. Der Multipart-Anbieter sucht den Nachrichtentextleser basierend auf dem Typ jedes Teils. Wenn es nicht auf application/json oder einen Typ gesetzt ist, hat die Foo oder Bar einen Reader für dies, schlägt dies fehl. Wir werden hier JSON verwenden. Es gibt keine zusätzliche Konfiguration, sondern einen Leser zur Verfügung zu haben. Ich werde Jackson benutzen. Mit der folgenden Abhängigkeit sollte keine andere Konfiguration erforderlich sein, da der Anbieter durch Klassenpfad-Scanning ermittelt wird.

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

Nun zum Test. Ich werde cURL verwenden. Sie können sehen, dass ich den Content-Type für jeden Teil explizit mit type setze. Der -F bezeichnet einen anderen Teil. (Siehe ganz unten im Beitrag für eine Vorstellung davon, wie der Anfragetext tatsächlich aussieht.)

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
Ergebnis:FooFoo; BarBar

Das Ergebnis ist genau wie erwartet. Wenn Sie sich die Ressourcenmethode ansehen, geben wir nur diesen String foo.foo + "; " + bar.bar zurück, der aus den beiden JSON-Objekten zusammengestellt wurde.

In den folgenden Links sehen Sie einige Beispiele mit verschiedenen JAX-RS-Clients. Einige serverseitige Beispiele finden Sie auch bei diesen verschiedenen JAX-RS-Implementierungen. Jeder Link sollte irgendwo einen Link zur offiziellen Dokumentation für diese Implementierung enthalten

Es gibt auch andere JAX-RS-Implementierungen, aber Sie müssen die Dokumentation dafür selbst finden. Die obigen drei sind die einzigen, mit denen ich Erfahrung habe.

Was Javascript-Clients anbelangt, sehe ich das meiste Beispiel (zB einige davon ), indem Sie Content-Type auf undefined/false setzen (mit FormData) und den Browser damit umgehen lassen. Dies funktioniert jedoch nicht für uns Der Browser setzt den Content-Type nicht für jeden Teil. Der Standardtyp ist text/plain.

Ich bin mir sicher, dass es Bibliotheken gibt, in denen der Typ für jeden Teil festgelegt werden kann. Um nur zu zeigen, wie dies manuell geschehen kann, werde ich ein Beispiel posten (habe etwas von hier bekommen.) Ich werde Angular verwenden, aber die grunzige Arbeit beim Aufbau des Entity-Körpers ist einfach altes 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>

Der interessante Teil ist die Funktion createRequest. Hier erstellen wir den Multipart, setzen den Content-Type jedes Teils auf application/json und verketten die stringifizierten foo- und bar-Objekte mit jedem Teil. Wenn Sie kein vertrautes Multipart-Format haben siehe hier für weitere Informationen . Der andere interessante Teil ist der Header. Wir setzen es auf multipart/form-data

Unten ist das Ergebnis. In Angular habe ich das Ergebnis verwendet, um es mit $scope.result = response.data im HTML anzuzeigen. Die Schaltfläche, die Sie sehen, bestand nur aus der Anforderung. Sie sehen die Anforderungsdaten auch in firebug

enter image description here

3. Wickeln Sie sie einfach in ein einzelnes übergeordnetes Objekt

Diese Option sollte selbsterklärend sein, wie andere bereits erwähnt haben.

31
Paul Samsotha

Der nächste Ansatz wird normalerweise in solchen Fällen angewendet:

TransferObject {
    ObjectOne objectOne;
    ObjectTwo objectTwo;

    //getters/setters
}

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

Sie können nicht zwei separate Objekte in einen einzigen POST - Aufruf einfügen, wie von Tarlog beschrieben.

Sie können jedoch ein drittes Containerobjekt erstellen, das die ersten beiden Objekte enthält, und dieses innerhalb des POS-Aufrufs übergeben.

4
Giorgio

Ich habe auch mit diesem Problem konfrontiert. Vielleicht wird das helfen.

@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

Ändern Sie @Consumes (MediaType.APPLICATION_JSON) In @Consumes ({MediaType.APPLICATION_FORM_URLENCODED}) Anschließend können Sie mehrere Objekte in den Hauptteil einfügen

0
alexBai

Dies kann erreicht werden, indem die Methode POST für die Aufnahme von Arrays von Objekten deklariert wird. Beispiel wie folgt

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