简体   繁体   English

是否有针对Java的事件驱动的JSON REST客户端API?

[英]Is there an event-driven JSON REST client API for Java?

I have a Java application which uses Spring's RestTemplate API to write concise, readable consumers of JSON REST services: 我有一个Java应用程序,它使用Spring的RestTemplate API编写简洁,可读的JSON REST服务使用者:

In essence: 在本质上:

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

The JSON response contains a list of items, and as you can see, I have have an event-driven design in my own code to handle each item in turn. JSON响应包含一个项目列表,正如您所看到的,我在自己的代码中有一个事件驱动的设计来依次处理每个项目。 However, the entire list is in memory as part of response , which RestTemplate.exchange() produces. 但是,整个列表在内存中作为response一部分, RestTemplate.exchange()生成。

I would like the application to be able to handle responses containing large numbers of items - say 50,000, and in this case there are two issues with the implementation as it stands: 我希望应用程序能够处理包含大量项目的响应 - 比如50,000,在这种情况下,实现有两个问题:

  1. Not a single item is handled until the entire HTTP response has been transferred - adding unwanted latency. 在传输整个HTTP响应之前,不会处理任何单个项目 - 增加不必要的延迟。
  2. The huge response object sits in memory and can't be GC'd until the last item has been handled. 巨大的响应对象位于内存中,直到最后一个项目被处理后才能进行GC。

Is there a reasonably mature Java JSON/REST client API out there that consumes responses in an event-driven manner? 是否有一个相当成熟的Java JSON / REST客户端API,以事件驱动的方式消耗响应?

I imagine it would let you do something like: 我想它可以让你做类似的事情:

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

I appreciate I could roll my own implementation of this behaviour with streaming JSON APIs from Jackson, GSON etc. -- but I'd love to be told there was something out there that does it reliably with a concise, expressive API, integrated with the HTTP aspect. 我很欣赏我可以使用来自Jackson,GSON等的流式JSON API来实现这种行为的实现 - 但是我很想知道那里有一些东西能够可靠地使用简洁,富有表现力的API,与HTTP方面。

A couple of months later; 几个月后; back to answer my own question. 回来回答我自己的问题。

I didn't find an expressive API to do what I want, but I was able to achieve the desired behaviour by getting the HTTP body as a stream, and consuming it with a Jackson JsonParser : 我没有找到一个表达的API来做我想要的,但我能够通过将HTTP主体作为流来实现所需的行为,并使用Jackson JsonParser消费它:

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

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

... with handleJsonStream designed to handle JSON that looks like this: ...使用handleJsonStream设计来处理如下所示的JSON:

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

... it validates the tokens leading up to the start of the array; ...它验证了导致阵列开始的令牌; it creates an Item object each time it encounters an array element, and gives it to the handler. 它每次遇到数组元素时都会创建一个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's OK to ignore remaining closing tokens.
     return count;
 }

verify() is just a private static method which throws an exception if the first two arguments aren't equal. verify()只是一个私有静态方法,如果前两个参数不相等则抛出异常。

The key thing about this method is that no matter how many items there are in the stream, this method only every has a reference to one Item. 关于这个方法的关键是无论流中有多少项,这个方法只有每个都有一个Item的引用。

Is there no way to break up the request? 有没有办法打破这个要求? It sounds like you should use paging. 听起来你应该使用分页。 Make it so that you can request the first 100 results, the next 100 results, so on. 使它可以请求前100个结果,接下来的100个结果,依此类推。 The request should take a starting index and a count number. 请求应该采用起始索引和计数。 That's very common behavior for REST services and it sounds like the solution to your problem. 这是REST服务非常常见的行为,听起来像是解决问题的方法。

The whole point of REST is that it is stateless, it sounds like you're trying to make it stateful. REST的全部意义在于它是无状态的,听起来你正试图让它成为有状态的。 That's anathema to REST, so you're not going to find any libraries written that way. 这是REST的诅咒,所以你不会找到任何以这种方式编写的库。

The transactional nature of REST is very much intentional by design and so you won't get around that easily. REST的事务性质在设计上是非常有意的,因此您不会轻易解决这个问题。 You'll fighting against the grain if you try. 如果你尝试的话,你会对抗粮食。

From what I've seen, wrapping frameworks (like you are using) make things easy by deserializing the response into an object. 从我所看到的情况来看,包装框架(就像你正在使用的那样)通过将响应反序列化为对象来简化操作。 In your case, a collection of objects. 在您的情况下,一组对象。

However, to use things in a streaming fashion, you may need to get at the underlying HTTP response stream. 但是,要以流式方式使用内容,您可能需要获取底层HTTP响应流。 I am most familiar with Jersey, which exposes https://jersey.java.net/nonav/apidocs/1.5/jersey/com/sun/jersey/api/client/ClientResponse.html#getEntityInputStream() 我最熟悉泽西岛,它暴露了https://jersey.java.net/nonav/apidocs/1.5/jersey/com/sun/jersey/api/client/ClientResponse.html#getEntityInputStream()

It would be used by invoking 它将被调用使用

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

This provides you with the stream of data coming in. The next step is to write the streaming part. 这为您提供了数据流。下一步是编写流媒体部分。 Given that you are using JSON, there are options at various levels, including http://wiki.fasterxml.com/JacksonStreamingApi or http://argo.sourceforge.net/documentation.html . 鉴于您使用的是JSON,有各种级别的选项,包括http://wiki.fasterxml.com/JacksonStreamingApihttp://argo.sourceforge.net/documentation.html They can consume the InputStream. 他们可以使用InputStream。

These don't really make good use of the full deserialization that can be done, but you could use them to parse out an element of a json array, and pass that item to a typical JSON object mapper, (like Jackson, GSON, etc). 这些并没有真正充分利用可以完成的完全反序列化,但你可以使用它们来解析json数组的元素,并将该项传递给典型的JSON对象映射器(如Jackson,GSON等) )。 This becomes the event handling logic. 这成为事件处理逻辑。 You could spawn new threads for this, or do whatever your use case needs. 您可以为此生成新线程,或者根据您的用例需要执行任何操作。

you can try JsonSurfer which is designed to process json stream in event-driven style. 您可以尝试使用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());

I won't claim to know all the rest frameworks out there (or even half) but I'm going to go with the answer 我不会声称知道所有其他框架(甚至一半),但我会回答

Probably Not 可能不是

As noted by others this is not the way REST normally thinks of it's interactions. 正如其他人所指出的,这不是REST通常认为它的相互作用的方式。 REST is a great Hammer but if you need streaming, you are (IMHO) in screwdriver territory, and the hammer might still be made to work, but it is likely to make a mess. REST是一个很棒的锤子,但如果你需要流媒体,你就是(恕我直言)在螺丝刀领域,锤子可能仍然可以工作,但它可能会弄得一团糟。 One can argue that it is or is not consistent with REST all day long, but in the end I'd be very surprised to find a framework that implemented this feature. 有人可能会说它整天都与REST一致或不一致,但最终我会惊讶地发现一个实现此功能的框架。 I'd be even more surprised if the feature is mature (even if the framework is) because with respect to REST your use case is an uncommon corner case at best. 如果该功能成熟(即使是框架),我会更加惊讶,因为就REST而言,您的用例充其量是一个不常见的极端情况。

If someone does come up with one I'll be happy to stand corrected and learn something new though :) 如果有人想出一个,我会很高兴能够纠正并学习一些新东西:)

Perhaps it would be best to be thinking in terms of comet or websockets for this particular operation. 对于这种特殊操作,最好是考虑彗星或腹板。 This question may be helpful since you already have spring. 这个问题可能有用,因为你已经有了春天。 (websockets are not really viable if you need to support IE < 10, which most commercial apps still require... sadly, I've got one client with a key customer still on IE 7 in my personal work) (如果你需要支持IE <10,那么websockets 也不是真的可行 ,大多数商业应用程序仍需要它......遗憾的是,在我的个人工作中,我有一个关键客户仍在IE 7上的客户端)

You may consider Restlet . 你可以考虑Restlet

http://restlet.org/discover/features http://restlet.org/discover/features

Supports asynchronous request processing, decoupled from IO operations. 支持异步请求处理,与IO操作分离。 Unlike the Servlet API, the Restlet applications don't have a direct control on the outputstream, they only provide output representation to be written by the server connector. 与Servlet API不同,Restlet应用程序没有对输出流的直接控制,它们仅提供由服务器连接器写入的输出表示。

The best way to achieve this is to use another streaming Runtime for JVM that allows reading response off websockets and i am aware of one called atmostphere 实现这一目标的最佳方法是使用另一个用于JVM的流式运行时,它允许从websockets读取响应,并且我知道一个名为atmostphere的响应

This way your large dataset is both sent and received in chunks on both side and read in the same manner in realtime withou waiting for the whole response. 这样,您的大型数据集既可以在两侧以块的形式发送和接收,也可以以相同的方式实时读取,同时等待整个响应。

This has a good POC on this: http://keaplogik.blogspot.in/2012/05/atmosphere-websockets-comet-with-spring.html 这有一个很好的POC: http//keaplogik.blogspot.in/2012/05/atmosphere-websockets-comet-with-spring.html

Server: 服务器:

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

Client: Atmosphere has it's own javascript file to handle the different Comet/Websocket transport types and requests. 客户端:Atmosphere拥有自己的javascript文件来处理不同的Comet / Websocket传输类型和请求。 By using this, you can set the Spring URL Controller method endpoint to the request. 通过使用它,您可以将Spring URL Controller方法端点设置为请求。 Once subscribed to the controller, you will receive dispatches, which can be handled by adding a request.onMessage method. 订阅控制器后,您将收到调度,可以通过添加request.onMessage方法来处理调度。 Here is an example request with transport of websockets. 这是一个带有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.");
        }
    }
   }

It has support on all major browsers and native mobile clients Apple being pioneers of this technology: 它支持所有主流浏览器和原生移动客户端Apple是该技术的先驱:

As mentioned here excellent support for deployment environments on web and enterprise JEE containers: 如此处所述,对Web和企业JEE容器上的部署环境提供了出色的支持:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM