JAX-RS розміщувати кілька об’єктів


75

У мене є метод;

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

Тепер я знаю, що можу опублікувати один об’єкт у форматі json, просто помістивши його в тіло. Але чи можна робити кілька об’єктів? Якщо так, то як?

Відповіді:


67

Відповідь - ні .

Причина проста: Це про параметри, які ви можете отримати в методі. Вони повинні бути пов’язані із запитом. Правда? Тому вони повинні бути або заголовками, або файлами cookie, або параметрами запиту, або параметрами матриці, або параметрами шляху, або тілом запиту . (Щоб розповісти повну історію, є додаткові типи параметрів, які називаються контекстом).

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


2
@Scholle Цікаво, як ви вирішили, що моя відповідь стосується наступних запитів?
Тарлог

Мммм, у списку <SomeEntity> є кілька об'єктів, чи не так?
Stijn de Witt

Масив @StijndeWitt може містити кілька об’єктів. Це мета масиву, так? API JAX-RS не може приймати кілька об'єктів. Отже, як зазначає у своїй відповіді tine2k: ви можете мати об’єкт-контейнер, який міститиме кілька об’єктів.
Тарлог

85

Ви не можете використовувати свій метод таким чином, як правильно вказав 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
Останній (із QueryParam) не працює. Помилка - "Сервер відхилив цей запит, оскільки сутність запиту має формат, який не підтримується запитуваним ресурсом для запитуваного методу (непідтримуваний тип носія)"
Санджай Кумар,

@ tine2k для мене List<Object> objects is not workingтут я
розміщу

1
Новачкам Java: не гугл ObjectOne, це не спеціальний клас об’єкта, а фіктивне ім’я, яке потрібно замінити власним іменем класу.
Skippy le Grand Gourou

36

Якщо ми подивимося, що намагається зробити OP, він / вона намагається опублікувати два (можливо, не пов’язані) об’єкти JSON. По-перше, будь-яке рішення, щоб спробувати надіслати одну частину як тіло, а одну частину як інший параметр, ІМО, є жахливими рішеннями. Дані POST повинні надходити в тіло. Не правильно робити щось лише тому, що це працює. Деякі робочі процедури можуть порушувати основні принципи REST.

Я бачу кілька рішень

  1. Використовуйте application / x-www-form-urlencoded
  2. Використовуйте Multipart
  3. Просто оберніть їх одним батьківським об’єктом

1. Використовуйте application / x-www-form-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

Одним із рішень, про яке ніхто не згадував, є використання багаточастин . Це дозволяє нам надсилати довільні частини у запиті. Оскільки кожен запит може мати лише одне тіло сутності, мультичастиною є обхід, оскільки він дозволяє мати різні частини (зі своїми власними типами вмісту) як частину тіла сутності.

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

Залежність

<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-Typeundefined / 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

введіть тут опис зображення

3. Просто оберніть їх одним батьківським об’єктом

Цей варіант повинен бути зрозумілим, як уже згадували інші.


8

У таких випадках зазвичай застосовують наступний підхід:

TransferObject {
    ObjectOne objectOne;
    ObjectTwo objectTwo;

    //getters/setters
}

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

4

Ви не можете помістити два окремі об'єкти в один виклик POST, як пояснив Tarlog.

У будь-якому випадку ви можете створити третій об'єкт-контейнер, який містить перші два об'єкти, і передати його в рамках виклику POS.


2

Я також стикався з цією проблемою. Можливо, це допоможе.

@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

Це можна зробити, оголосивши метод POST, що приймає масив об'єктів. Приклад, подібний до цього

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

0

Змініть @Consumes (MediaType.APPLICATION_JSON) на @Consumes ({MediaType.APPLICATION_FORM_URLENCODED}). Тоді ви зможете помістити в тіло кілька об’єктів

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.