JAX-RS Размещать несколько объектов - PullRequest
73 голосов
/ 05 апреля 2011

У меня есть метод;

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

Теперь я знаю, что могу опубликовать один объект в формате json, просто поместив его в тело. Но возможно ли сделать несколько объектов? Если да, то как?

Ответы [ 8 ]

84 голосов
/ 08 января 2013

Вы не можете использовать ваш метод, как это правильно указано в Tarlog.

Однако вы можете сделать это:

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

или это:

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

Кроме того, вы всегда можете комбинировать свой метод с параметрами GET:

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(List<ObjectOne> objects, @QueryParam("objectTwoId") long objectTwoId)
62 голосов
/ 06 апреля 2011

Ответ нет .

Причина проста: это параметры, которые вы можете получить в методе.Они должны быть связаны с запросом.Правильно?Поэтому они должны быть либо заголовками, либо файлами cookie, либо параметрами запроса, либо параметрами матрицы, либо параметрами пути, либо телом запроса (Просто, чтобы рассказать полную историю, есть дополнительные типы параметров, называемые контекстом).

Теперь, когда вы получаете объект JSON в вашем запросе, вы получаете его в теле запроса .Сколько тел может иметь запрос?Один и только один.Таким образом, вы можете получить только один объект JSON.

31 голосов
/ 27 марта 2015

Если мы посмотрим, что пытается сделать OP, он / она пытается опубликовать два (возможно, не связанных) объекта JSON. Сначала любое решение, чтобы попытаться отправить одну часть как тело, а другую как другой параметр, IMO, - это ужасные решения. Данные POST должны идти в теле. Это не правильно делать что-то только потому, что это работает. Некоторые обходные пути могут нарушать основные принципы REST.

Я вижу несколько решений

  1. Используйте application / x-www-form-urlencoded
  2. Использовать Multipart
  3. Просто оберните их в один родительский объект

1. Используйте application / x-www-form-urlencoded

Другой вариант - просто использовать application/x-www-form-urlencoded. На самом деле мы можем иметь значения JSON. Например: 1018 *

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

Единственное, что нам нужно, чтобы заставить это работать, это ParamConverterProvider, чтобы заставить это работать. Ниже приведен пример, который был реализован Михалом Гаджосом из команды Джерси (найден здесь с объяснением ).

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

Если вы не сканируете ресурсы и провайдеров, просто зарегистрируйте этого провайдера, и приведенный выше пример должен работать.

2. Использовать Multipart

Одним из решений, о котором никто не упомянул, является использование multipart . Это позволяет нам отправлять произвольные части в запросе. Поскольку каждый запрос может иметь только одно тело сущности, multipart - это обходной путь, поскольку он позволяет иметь разные части (со своими собственными типами контента) как часть тела сущности.

Вот пример использования Джерси (см. Официальный документ здесь )

Зависимость

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

Зарегистрировать 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);
    }
}

Ресурсный класс

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

Теперь сложность некоторых клиентов заключается в том, что нет способа установить Content-Type каждой части тела, что необходимо для работы вышеупомянутого. Многокомпонентный провайдер будет искать программу чтения сообщений в зависимости от типа каждой части. Если для него не установлено значение application/json или тип, для которого у Foo или Bar есть считыватель, это не удастся. Мы будем использовать JSON здесь. Там нет никакой дополнительной конфигурации, кроме наличия ридера. Я буду использовать Джексона. С учетом приведенной ниже зависимости никакая другая конфигурация не требуется, поскольку поставщик будет обнаружен при сканировании пути к классам.

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

Теперь тест. Я буду использовать cURL . Вы можете видеть, что я явно установил Content-Type для каждой части с type. -F означает другую часть. (Смотрите в самом конце поста идею того, как на самом деле выглядит тело запроса.)

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
Результат: FooFoo; BarBar

Результат точно такой, как мы ожидали. Если вы посмотрите на метод ресурса, все, что мы делаем, это возвращаем эту строку foo.foo + "; " + bar.bar, собранную из двух объектов JSON.

Вы можете увидеть некоторые примеры использования различных клиентов JAX-RS по ссылкам ниже. Вы также увидите некоторый пример на стороне сервера также из этих различных реализаций JAX-RS. Каждая ссылка должна иметь где-то ссылку на официальную документацию для этой реализации

Существуют и другие реализации JAX-RS, но вам нужно будет найти документацию для них самостоятельно. Вышеупомянутые три - единственные, с которыми я имею опыт.

Что касается клиентов Javascript, большинство примеров, которые я вижу (например, , некоторые из них , включают установку Content-Type в undefined / false (с использованием FormData), позволяя браузеру обрабатывать его. Но это не будет работать для нас, так как браузер не будет устанавливать Content-Type для каждой части. И тип по умолчанию - text/plain.

Я уверен, что есть библиотеки, которые позволяют устанавливать тип для каждой части, но просто чтобы показать вам, как это можно сделать вручную, я опубликую пример (небольшая помощь от здесь . Я буду использовать Angular, но тяжелая работа по созданию тела сущности будет простым старым 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>

Интересной частью является функция createRequest.Здесь мы строим составную часть, устанавливая Content-Type каждой части на application/json и объединяя строковые объекты foo и bar с каждой частью.Если вы не знакомы с многочастным форматом , см. Дополнительную информацию .Другая интересная часть - заголовок.Мы установили его на multipart/form-data.

Ниже приведен результат.В Angular я просто использовал результат для отображения в HTML с $scope.result = response.data.Кнопка, которую вы видите, была просто чтобы сделать запрос.Вы также увидите данные запроса в firebug

enter image description here

3.Просто оберните их в один родительский объект

Этот параметр должен быть понятен, как уже упоминали другие.

8 голосов
/ 01 марта 2015

В таких случаях обычно применяется следующий подход:

TransferObject {
    ObjectOne objectOne;
    ObjectTwo objectTwo;

    //getters/setters
}

@POST
@Path("test")
@Consumes(MediaType.APPLICATION_JSON)
public void test(TransferObject object){
//        object.getObejctOne()....
}
4 голосов
/ 16 июля 2012

Вы не можете поместить два отдельных объекта в один вызов POST, как объяснено Tarlog.

В любом случае вы можете создать третий объект-контейнер, который содержит первые два объекта и передать его в вызове POS.

2 голосов
/ 27 марта 2015

Я тоже сталкивался с этой проблемой. Может быть, это поможет.

@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@gmail.com",                 "country":"Azeribaijan","city":"Baku","paste_date":"20150327"},
        "someThingElse":"String type of element"        
    }); 
} //-- END of Complex Object
});// -- END of JQuery -  Ajax
1 голос
/ 09 ноября 2016

Это можно сделать, объявив метод POST для приема массива объектов. Пример как это

T[] create(@RequestBody T[] objects) {
for( T object : objects ) {
   service.create(object);
  }
}
0 голосов
/ 21 января 2019

Измените @Consumes (MediaType.APPLICATION_JSON) на @Consumes ({MediaType.APPLICATION_FORM_URLENCODED}) Затем вы можете поместить несколько объектов в тело

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...