Подтвердить что ты не робот

JAX-RS Сообщение нескольких объектов

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

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

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

4b9b3361

Ответ 1

Ответ нет.

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

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

Ответ 2

Вы не можете использовать свой метод, как это, как правильно указано 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)

Ответ 3

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

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

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

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

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

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. Это позволяет нам отправлять произвольные части в запросе. Поскольку каждый запрос может иметь только одно тело объекта, обходной стороной является обход, так как он позволяет иметь разные части (со своими собственными типами контента) как часть тела объекта.

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

зависимость

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

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

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

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

Что касается клиентов Javascript, большинство примеров, которые я вижу (например, некоторые из них, включают установку Content-Type на undefined/false (с использованием FormData), позволяя браузеру обрабатывать его. Но это не будет работать для нас, как Browser не будет устанавливать 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. Просто оберните их в один родительский объект

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

Ответ 4

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

TransferObject {
    ObjectOne objectOne;
    ObjectTwo objectTwo;

    //getters/setters
}

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

Ответ 5

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

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

Ответ 6

Я также столкнулся с этой проблемой. Возможно, это поможет.

@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

Ответ 7

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

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

Ответ 8

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