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

Как передать сложные объекты в качестве аргументов службе RESTful?

Я успешно настроил быстрый тест для создания "REST-подобного" сервиса, который возвращает объект, сериализованный в JSON, и это было довольно просто и быстро (на основе этой статьи).

Но хотя возвращать JSON-ified объекты было легко, как персику, я еще не видел примеров, связанных с входными параметрами, которые не являются примитивами. Как я могу передать сложный объект в качестве аргумента? Я использую Apache CXF, но также приветствуются примеры использования других фреймворков, таких как Джексон :)

На стороне клиента, вероятно, будет что-то вроде создания объекта javascript, передачи его в JSON.stringify(complexObj) и передачи этой строки в качестве одного из параметров.

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

@Service("myService")
class RestService {
    @GET
    @Produces("application/json")
    @Path("/fooBar")
    public Result fooBar(@QueryParam("foo") double foo, @QueryParam("bar") double bar,
        @QueryParam("object") MyComplex object) throws WebServiceException {
    ...
    }
}

Отправка сериализованных объектов в качестве параметров, вероятно, быстро коснется ограничения URL-адреса в 2 КБ, наложенного Internet Explorer. Вы бы порекомендовали использовать POST в этих случаях, и мне нужно было бы сильно изменить определения функций?

4b9b3361

Ответ 1

Немного покопавшись, я быстро обнаружил, что есть два основных варианта:

Опция 1

Вы передаете "объект-обертку", содержащий все остальные параметры, службе. Возможно, вам потребуется аннотировать этот класс-оболочку аннотациями JAXB, такими как @XmlRootElement, чтобы он работал с провайдером на основе Jettison, но если вы используете Джексона вместо него, в этом нет необходимости. Просто установите тип контента на правильный тип, и будет вызван правильный читатель тела сообщения. Конечно, это будет работать только для услуг типа POST (AFAIK).

пример

Это всего лишь пример превращения службы, упомянутой в первоначальном вопросе, в службу, использующую объект-оболочку.

@Service("myService")
class RestService {

    @POST
    @Produces("application/json")
    @Path("/fooBar")
    public Result fooBar(

          /** 
          * Using "" will inject all form params directly into a ParamsWrapper 
          * @see http://cxf.apache.org/docs/jax-rs-basics.html
          */
          @FormParam("") FooBarParamsWrapper wrapper

        ) throws WebServiceException {
            doSomething(wrapper.foo);
    }
}

class ParamsWrapper {
  double foo, bar;
  MyComplexObject object;
}

Вариант 2

Вы можете предоставить некоторый специальный формат строки, в который вы упаковываете ваши объекты, а затем реализуете конструктор, принимающий строку, статический valueOf (String s) или статический fromString (String s) в классе, который будет принимать эту строку и создавать объект от него. Или совсем аналогично, создайте ParameterHandler, который делает то же самое.

AFAIK, только вторая версия позволит вам вызывать ваши сервисы из браузера, используя JSONP (поскольку JSONP - это трюк, ограниченный GET). Я выбрал этот маршрут, чтобы можно было передавать массивы сложных объектов в URI.

В качестве примера того, как это работает, возьмем следующий класс домена и службу

пример

@GET
@Path("myService")
public void myService(@QueryParam("a") MyClass [] myVals) {
    //do something
}

class MyClass {
    public int foo;
    public int bar;

   /** Deserializes an Object of class MyClass from its JSON representation */
   public static MyClass fromString(String jsonRepresentation) {
           ObjectMapper mapper = new ObjectMapper(); //Jackson JSON marshaller
           MyClass o= null;
           try {
                   o = mapper.readValue(jsonRepresentation, MyClass.class );
           } catch (IOException e) {
                    throw new WebApplicationException()
           }
           return o;
   }
}

URI http://my-server.com/myService?a={"foo":1, "bar":2}&a={"foo":100, "bar":200} в этом случае будет десериализован в массив, состоящий из двух объектов MyClass.

Комментарий 2019 года. Видя, что в 2019 году этот ответ по-прежнему встречается, я чувствую, что должен это прокомментировать. Оглядываясь назад, я бы не рекомендовал вариант 2, поскольку выполнение этих шагов только для того, чтобы иметь возможность выполнять вызовы GET, добавляет сложности, которая, вероятно, того не стоит. Если ваш сервис принимает такие сложные входные данные, вы, вероятно, не сможете использовать кэширование на стороне клиента из-за количества перестановок вашего ввода. Я бы просто настроил правильные заголовки Cross-Origin-Sharing (CORS) на сервере и POST-ввод. Затем сконцентрируйтесь на кэшировании всего, что можете на сервере.

Ответ 2

Принятый ответ отсутствует @BeanParam. См. Https://docs.jboss.org/resteasy/docs/3.0-rc-1/javadocs/javax/ws/rs/BeanParam.html для получения дополнительной информации. Это позволяет вам определять параметры запроса внутри объекта-оболочки. Например

public class TestPOJO {

    @QueryParam("someQueryParam")
    private boolean someQueryParam;

    public boolean isSomeQueryParam() {
        return someQueryParam;
    }

    public boolean setSomeQueryParam(boolean value) {
        this.someQueryParam = value;
    }
}

... // inside the Resource class
@GET
@Path("test")
public Response getTest(@BeanParam TestPOJO testPOJO) {
    ...
}

Ответ 3

лучшим и простым решением является отправка вашего объекта в виде строки json, а на стороне сервера - реализация метода, который будет декодировать этот json и сопоставить указанный объект в соответствии с вашими потребностями.. и да, лучше использовать POST.