JAX-RS Mehrere Objekte veröffentlichen

75

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 und es einfach in den Körper einfügen kann. Aber ist es möglich, mehrere Objekte zu erstellen? Wenn das so ist, wie?

Thizzer
quelle

Antworten:

67

Die Antwort ist nein .

Der Grund ist einfach: Dies betrifft 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 Anforderungshauptteil sein . (Um die ganze Geschichte zu erzählen, gibt es zusätzliche Arten von Parametern, die als Kontext bezeichnet werden.)

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

Tarlog
quelle
2
@Scholle Ich frage mich, wie haben Sie entschieden, dass meine Antwort sich auf nachfolgende Anfragen bezieht?
Tarlog
Mmmm eine Liste <SomeEntity> hat mehrere Objekte, nein?
Stijn de Witt
@StijndeWitt Array kann mehrere Objekte enthalten. Das ist der Zweck des Arrays, oder? Die JAX-RS-API kann nicht mehrere Objekte empfangen. Wie tine2k in seiner Antwort feststellt: Sie können ein Containerobjekt haben, das mehrere Objekte enthält.
Tarlog
85

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

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 jederzeit mit GET-Parametern kombinieren:

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(List<ObjectOne> objects, @QueryParam("objectTwoId") long objectTwoId)
tine2k
quelle
3
Der letzte (mit QueryParam) funktioniert nicht. Fehler - "Der Server hat diese Anforderung abgelehnt, da die Anforderungsentität in einem Format vorliegt, das von der angeforderten Ressource für die angeforderte Methode (nicht unterstützter Medientyp) nicht unterstützt wird"
Sanjay Kumar
@ Tine2k für mich List<Object> objects is not workinghier Ich poste meinen Problem Link
JustStartedProgramming
1
Für Java-Neulinge: Google nicht ObjectOne, es ist keine spezielle Objektklasse, sondern ein Dummy-Name, den Sie durch Ihren eigenen Klassennamen ersetzen müssen.
Skippy le Grand Gourou
36

Wenn wir uns ansehen, was das OP versucht, versucht er / sie, zwei (möglicherweise nicht verwandte) JSON-Objekte zu veröffentlichen. Erstens ist jede Lösung, um zu versuchen, einen Teil als Körper und einen Teil als einen anderen Parameter, IMO, zu senden, schreckliche Lösungen. POST-Daten sollten in den Körper gelangen. Es ist nicht richtig, etwas zu tun, nur weil es funktioniert. Einige Workarounds verstoßen möglicherweise gegen grundlegende REST-Prinzipien.

Ich sehe ein paar 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, nur zu verwenden application/x-www-form-urlencoded. 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;
    }
}

Das einzige, was wir brauchen, um dies zum Laufen zu bringen, ist ParamConverterProvider, dass dies funktioniert. Unten ist eine, die von Michal Gadjos vom Jersey Team implementiert wurde ( 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 Anbietern suchen, registrieren Sie einfach diesen Anbieter. Das obige Beispiel sollte funktionieren.

2. Verwenden Sie Multipart

Eine Lösung, die niemand erwähnt hat, ist die Verwendung von mehrteiligen . Dies ermöglicht es uns, beliebige Teile in einer Anfrage zu senden. Da jede Anforderung nur einen Entitätstext haben kann, ist Multipart die Problemumgehung, da verschiedene Teile (mit eigenen Inhaltstypen) als Teil des Entitätstexts verwendet werden 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;
    }
}

Nun ist der schwierige Teil bei einigen Kunden, dass es keine Möglichkeit gibt, die Content-Typeeinzelnen Körperteile festzulegen, die erforderlich sind, damit die oben genannten Funktionen funktionieren. Der mehrteilige Anbieter sucht nach dem Nachrichtentextleser, basierend auf dem Typ jedes Teils. Wenn es nicht auf application/jsonoder einen Typ eingestellt ist, für den Foooder Barein Reader vorhanden ist, schlägt dies fehl. Wir werden hier JSON verwenden. Es gibt keine zusätzliche Konfiguration, als einen Reader zur Verfügung zu haben. Ich werde Jackson benutzen. Mit der folgenden Abhängigkeit sollte keine andere Konfiguration erforderlich sein, da der Anbieter durch das Scannen von Klassenpfaden erkannt wird.

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

Nun der Test. Ich werde cURL verwenden . Sie sehen, ich setze das explizit Content-Typefür jedes Teil mit type. Das -Fbedeutet zu einem anderen Teil. (Eine Vorstellung davon, wie der Anfragetext tatsächlich aussieht, finden Sie ganz unten im Beitrag.)

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 wir erwartet hatten. Wenn Sie sich die Ressourcenmethode ansehen, geben wir nur diese Zeichenfolge zurück foo.foo + "; " + bar.bar, die aus den beiden JSON-Objekten stammt.

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

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

Was Javascript-Clients betrifft, sehe ich die meisten Beispiele (z. B. einige davon beinhalten das Setzen Content-Typevon undefined / false (using FormData), damit der Browser damit umgehen kann. Dies funktioniert jedoch nicht für uns, da der Browser das nicht setzt Content-Typefür jedes Teil. Und der Standardtyp ist text/plain.

Ich bin mir sicher, dass es Bibliotheken gibt, in denen der Typ für jedes Teil festgelegt werden kann. Um Ihnen jedoch zu zeigen, wie dies manuell durchgeführt werden kann, werde ich ein Beispiel veröffentlichen (von hier aus ein wenig Hilfe erhalten . Ich werde Angular verwenden , aber die grunzende Arbeit beim Aufbau des Entitätskörpers wird einfaches altes Javascript sein.

<!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 createRequestFunktion. Hier erstellen wir das Multipart, setzen das Content-Typevon jedem Teil auf application/jsonund verketten das Stringified foound die barObjekte zu jedem Teil. Wenn Sie mit dem mehrteiligen Format nicht vertraut sind, finden Sie hier 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 nur verwendet, um es im HTML-Code mit anzuzeigen $scope.result = response.data. Die Schaltfläche, die Sie sehen, diente nur dazu, die Anfrage zu stellen. Sie sehen auch die Anforderungsdaten in Firebug

Geben Sie hier die Bildbeschreibung ein

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

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

Paul Samsotha
quelle
8

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()....
}
Oleksii Kyslytsyn
quelle
4

Sie können nicht zwei separate Objekte in einen einzelnen POST-Aufruf einfügen, wie von Tarlog erläutert.

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

Giorgio
quelle
2

Ich habe mich auch mit diesem Problem konfrontiert. Vielleicht hilft das.

@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
Musa
quelle
1

Dies kann erreicht werden, indem die POST-Methode deklariert wird, um ein Array von Objekten zu akzeptieren. Beispiel wie dieses

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

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

alexBai
quelle