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

Существует ли API-интерфейс JSON REST для Java?

У меня есть приложение Java, которое использует Spring RestTemplate API для написания кратких, читаемых пользователей служб JSON REST:

По существу:

 RestTemplate rest = new RestTemplate(clientHttpRequestFactory);
 ResponseEntity<ItemList> response = rest.exchange(url,
            HttpMethod.GET,     
            requestEntity,
            ItemList.class);

 for(Item item : response.getBody().getItems()) {
        handler.onItem(item);
 }

Ответ JSON содержит список элементов, и, как вы можете видеть, у меня есть управляемый событиями проект в моем собственном коде для обработки каждого элемента по очереди. Однако весь список находится в памяти как часть response, который создает RestTemplate.exchange().

Я хотел бы, чтобы приложение могло обрабатывать ответы, содержащие большое количество элементов - например, 50 000, и в этом случае есть две проблемы с реализацией:

  • Ни один элемент не обрабатывается до тех пор, пока весь HTTP-ответ не будет перенесен - добавление нежелательной задержки.
  • Огромный объект ответа находится в памяти и не может быть GC'd до тех пор, пока не будет обработан последний элемент.

Существует ли достаточно зрелый API-интерфейс Java JSON/REST, который потребляет ответы в зависимости от событий?

Я предполагаю, что это позволит вам сделать что-то вроде:

 RestStreamer rest = new RestStreamer(clientHttpRequestFactory);

 // Tell the RestStreamer "when, while parsing a response, you encounter a JSON
 // element matching JSONPath "$.items[*]" pass it to "handler" for processing.
 rest.onJsonPath("$.items[*]").handle(handler);

 // Tell the RestStreamer to make an HTTP request, parse it as a stream.
 // We expect "handler" to get passed an object each time the parser encounters
 // an item.
 rest.execute(url, HttpMethod.GET, requestEntity);

Я очень благодарен, что смог реализовать свою собственную реализацию этого поведения с помощью потоковых JSON-API от Jackson, GSON и т.д. - но мне бы хотелось, чтобы мне сказали, что есть что-то там, что делает это надежно с кратким, выразительным API, интегрированный с аспектом HTTP.

4b9b3361

Ответ 1

вы можете попробовать JsonSurfer, который предназначен для обработки потока json в стиле, управляемом событиями.

JsonSurfer surfer = JsonSurfer.jackson();
Builder builder = config();
builder.bind("$.items[*]", new JsonPathListener() {
        @Override
        public void onValue(Object value, ParsingContext context) throws Exception {
            // handle the value
        }
    });
surfer.surf(new InputStreamReader(response.getBody()), builder.build());

Ответ 2

Через пару месяцев; назад, чтобы ответить на мой собственный вопрос.

Я не нашел выразительного API, чтобы делать то, что хочу, но я смог добиться желаемого поведения, получив тело HTTP как поток и потребляя его с помощью Jackson JsonParser:

  ClientHttpRequest request = 
        clientHttpRequestFactory.createRequest(uri, HttpMethod.GET);
  ClientHttpResponse response = request.execute();

  return handleJsonStream(response.getBody(), handler);

... с handleJsonStream, предназначенным для обработки JSON, который выглядит следующим образом:

 { items: [ 
      { field: value; ... }, 
      { field: value, ... },
      ... thousands more ... 
 ] }

... он проверяет токены, ведущие к началу массива; он создает объект Item каждый раз, когда он встречает элемент массива и передает его обработчику.

 // important that the JsonFactory comes from an ObjectMapper, or it won't be
 // able to do readValueAs()
 static JsonFactory jsonFactory = new ObjectMapper().getFactory();

 public static int handleJsonStream(InputStream stream, ItemHandler handler) throws IOException {

     JsonParser parser = jsonFactory.createJsonParser(stream);

     verify(parser.nextToken(), START_OBJECT, parser);
     verify(parser.nextToken(), FIELD_NAME, parser);
     verify(parser.getCurrentName(), "items", parser);
     verify(parser.nextToken(), START_ARRAY, parser);
     int count = 0;
     while(parser.nextToken() != END_ARRAY) {
        verify(parser.getCurrentToken(), START_OBJECT, parser);
        Item item = parser.readValueAs(Item.class);
        handler.onItem(item);
        count++;
     }
     parser.close(); // hope it OK to ignore remaining closing tokens.
     return count;
 }

verify() - это просто частный статический метод, который генерирует исключение, если первые два аргумента не равны.

Ключевым моментом в этом методе является то, что независимо от количества элементов в потоке этот метод имеет только ссылку на один элемент.

Ответ 3

Нет ли способа разбить запрос? Похоже, вы должны использовать пейджинг. Сделайте так, чтобы вы могли запросить первые 100 результатов, следующие 100 результатов и так далее. Запрос должен содержать начальный индекс и номер счета. Это очень распространенное поведение для служб REST, и это похоже на решение вашей проблемы.

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

Транзакционный характер REST очень преднамерен дизайном, и поэтому вы не сможете обойти это легко. Если вы попробуете, вы будете сражаться против зерна.

Ответ 4

Из того, что я видел, обертывание фреймворков (например, вы используете) облегчает процесс десериализации ответа в объект. В вашем случае коллекция объектов.

Однако, чтобы использовать вещи в потоковом режиме, вам может потребоваться получить базовый поток ответов HTTP. Я больше всего знаком с Джерси, который предоставляет https://jersey.java.net/nonav/apidocs/1.5/jersey/com/sun/jersey/api/client/ClientResponse.html#getEntityInputStream()

Он будет использоваться при вызове

Client client = Client.create();
WebResource webResource = client.resource("http://...");
ClientResponse response = webResource.accept("application/json")
               .get(ClientResponse.class);
InputStream is = response.getEntityInputStream();

Это дает вам поток поступающих данных. Следующим шагом является запись потоковой части. Учитывая, что вы используете JSON, есть варианты на разных уровнях, включая http://wiki.fasterxml.com/JacksonStreamingApi или http://argo.sourceforge.net/documentation.html. Они могут потреблять InputStream.

На самом деле они не используют полную десериализацию, которую можно сделать, но вы можете использовать их для анализа элемента json-массива и передать этот элемент типичному объектно-ориентированному объекту JSON (например, Jackson, GSON и т.д.). Это становится логикой обработки событий. Вы можете создавать новые потоки для этого или делать все, что нужно для вашего использования.

Ответ 5

Я не буду утверждать, что знаю все остальные рамки там (или даже половину), но я собираюсь пойти с ответом

Вероятно, не

Как отмечают другие, это не способ, которым REST обычно думает об этом взаимодействии. REST - отличный молот, но если вам нужна потоковая передача, вы (IMHO) на территории отвертки, и молот все еще может работать, но это, вероятно, вызовет беспорядок. Можно утверждать, что он или не соответствует REST в течение всего дня, но в конце концов я был бы очень удивлен, найдя фреймворк, который реализовал эту функцию. Я был бы еще более удивлен, если функция будет зрелой (даже если это фреймворк), потому что в отношении REST ваш вариант использования - это, в лучшем случае, необычный угловой случай.

Если кто-то придумает один, я буду счастлив стоять исправленным и узнать что-то новое, хотя:)

Возможно, было бы лучше подумать о кометах или веб-сайтах для этой конкретной операции. Этот вопрос может быть полезен, так как у вас уже есть spring. (websockets не очень жизнеспособный, если вам нужно поддерживать IE < 10, что требуется большинству коммерческих приложений... к сожалению, я получил один клиент с ключевым клиентом, все еще на IE 7, в моей личной работе)

Ответ 6

Вы можете рассмотреть Restlet.

http://restlet.org/discover/features

Поддерживает обработку асинхронного запроса, отключенную от операций ввода-вывода. В отличие от API Servlet, приложения Restlet не имеют прямого управления выходным потоком, они предоставляют только представление вывода, которое должно быть записано соединителем сервера.

Ответ 7

Лучший способ добиться этого - использовать еще одну поточную Runtime для JVM, которая позволяет читать отклик от веб-гейтов, и я знаю об одной называемой atmostphere

Таким образом, ваш большой набор данных отправляется и принимается в кусках с обеих сторон и читается таким же образом в реальном времени, пока вы не ожидаете ответа в целом.

У этого есть хороший POC: http://keaplogik.blogspot.in/2012/05/atmosphere-websockets-comet-with-spring.html

Сервер:

    @RequestMapping(value="/twitter/concurrency")
@ResponseBody
public void twitterAsync(AtmosphereResource atmosphereResource){
    final ObjectMapper mapper = new ObjectMapper();

    this.suspend(atmosphereResource);

    final Broadcaster bc = atmosphereResource.getBroadcaster();

    logger.info("Atmo Resource Size: " + bc.getAtmosphereResources().size());

    bc.scheduleFixedBroadcast(new Callable<String>() {

        //@Override
        public String call() throws Exception {

            //Auth using keaplogik application springMVC-atmosphere-comet-webso key
            final TwitterTemplate twitterTemplate = 
                new TwitterTemplate("WnLeyhTMjysXbNUd7DLcg",
                        "BhtMjwcDi8noxMc6zWSTtzPqq8AFV170fn9ivNGrc", 
                        "537308114-5ByNH4nsTqejcg5b2HNeyuBb3khaQLeNnKDgl8",
                        "7aRrt3MUrnARVvypaSn3ZOKbRhJ5SiFoneahEp2SE");

            final SearchParameters parameters = new SearchParameters("world").count(5).sinceId(sinceId).maxId(0);
            final SearchResults results = twitterTemplate.searchOperations().search(parameters);

            sinceId = results.getSearchMetadata().getMax_id();

            List<TwitterMessage> twitterMessages = new ArrayList<TwitterMessage>();

            for (Tweet tweet : results.getTweets()) {
                twitterMessages.add(new TwitterMessage(tweet.getId(),
                                                       tweet.getCreatedAt(),
                                                       tweet.getText(),
                                                       tweet.getFromUser(),
                                                       tweet.getProfileImageUrl()));
            }

            return mapper.writeValueAsString(twitterMessages);
        }

    }, 10, TimeUnit.SECONDS);
}

Клиент: Атмосфера имеет собственный файл javascript для обработки различных типов и запросов транспорта Comet/Websocket. Используя это, вы можете установить конечную точку метода Spring URL Controller для запроса. После того как вы подписаны на контроллер, вы получите рассылки, которые можно обработать, добавив метод request.onMessage. Вот пример запроса с транспортом websockets.

       var request = new $.atmosphere.AtmosphereRequest();
   request.transport = 'websocket';
   request.url = "<c:url value='/twitter/concurrency'/>";
   request.contentType = "application/json";
   request.fallbackTransport = 'streaming';

   request.onMessage = function(response){
       buildTemplate(response);
   };

   var subSocket = socket.subscribe(request);

   function buildTemplate(response){

     if(response.state = "messageReceived"){

          var data = response.responseBody;

        if (data) {

            try {
                var result =  $.parseJSON(data);

                $( "#template" ).tmpl( result ).hide().prependTo( "#twitterMessages").fadeIn();

            } catch (error) {
                console.log("An error ocurred: " + error);
            }
        } else {
            console.log("response.responseBody is null - ignoring.");
        }
    }
   }

Он поддерживает все основные браузеры и мобильные клиенты Apple, являющиеся пионерами этой технологии:

Как уже упоминалось, отличная поддержка среды развертывания на веб-и корпоративных контейнерах JEE:

http://jfarcand.wordpress.com/2012/04/19/websockets-or-comet-or-both-whats-supported-in-the-java-ee-land/