将JSON直接流式传输到与Jackson的响应 [英] Streaming JSON directly to response with Jackson

查看:493
本文介绍了将JSON直接流式传输到与Jackson的响应的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目前,我需要将一个大型json对象发送到ajax请求。出于此目的,我使用以下控制器方法工作正常。

Currently, I need to send a large json object to an ajax request. For the purpose I am using the following controller method which works fine.

   @RequestMapping(method = RequestMethod.POST,params = {"dynamicScenario"})
   @ResponseBody
    public String getDynamicScenarioData(@RequestParam Map<String, String> map) throws JsonParseException, JsonMappingException, IOException
 {

  ObjectMapper mapper = new ObjectMapper();

    @SuppressWarnings("unchecked")
    Map<String,Object> queryParameters = mapper.readValue(map.get("parameters") , Map.class);

    Map<String, Object> getData = service.runDynamicScenario(queryParameters, map.get("queryString"));

    return writer.writeValueAsString(getData); //here java throws java.lang.OutOfMemoryError: Java heap space memory
} 

更新:
我的ajax是:

Update: my ajax is:

          $.ajax({
          type: "POST",
          url: "dynamicScenario.htm",
          data : tags,
          dataType: "json",
          success: function(data){});

我的DispatcherServlet设置:

My DispatcherServlet settings:

           public class ApplicationInitializer implements      WebApplicationInitializer
      {
       public void onStartup(ServletContext servletContext) throws      
     ServletException 
     {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    context.register(ApplicationConfig.class);

    servletContext.addListener(new ContextLoaderListener(context));

    ServletRegistration.Dynamic servletRegistration = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
    servletRegistration.setLoadOnStartup(1);
    servletRegistration.addMapping("*.htmlx");
  }
}

我使用jackson序列化不同对象的地图然后将其发送回ajax。但是,如果json的大小很大,那么java会抛出内存。我知道Jackson方法writer.writeValueAsString是低效的,因为它写入字符串但是还有其他选择吗?我不能使用普通的java POJO,因为我不知道我将要序列化的地图将包含哪些对象,所以我不能简单地将它映射到某个java对象。有任何想法吗?谢谢

I am using jackson to serialize a map of different objects and then send it back to the ajax. However, if the size of the json is large then java throws out of memory. I know the Jackson method writer.writeValueAsString is inefficient because its writing to a string but is there any other alternative? I can't use plain java POJO because I don't know what objects the map which I will have to serialize will contain so I can't simply map it to some java object. Any ideas? Thanks

推荐答案

您要解决的问题是

return writer.writeValueAsString(getData); 

创建太大的字符串并导致 OutOfMemoryError 。 Jackson支持 Streaming API ,Spring在其 MappingJackson2HttpMessageConverter中使用,它处理在响应主体(和请求主体)中将POJO序列化为JSON。

creating too big of a String and causing an OutOfMemoryError. Jackson supports a Streaming API, which Spring makes use of in its MappingJackson2HttpMessageConverter, which handles serializing POJOs to JSON in the response body (and request body).

有几种方法可以解决这个问题。最简单的方法是将返回类型更改为 Map< String,Object 并直接返回相应的对象。

There are a few ways to handle this. The easiest is to change your return type to Map<String, Object and return the corresponding object directly.

@RequestMapping(method = RequestMethod.POST,params = {"dynamicScenario"})
@ResponseBody
public Map<String, Object> getDynamicScenarioData(@RequestParam Map<String, String> map) throws JsonParseException, JsonMappingException, IOException
 {
     ObjectMapper mapper = new ObjectMapper();

     @SuppressWarnings("unchecked")
     Map<String,Object> queryParameters = mapper.readValue(map.get("parameters") , Map.class);

     Map<String, Object> getData = service.runDynamicScenario(queryParameters, map.get("queryString"));

     return getData;
} 

Spring将负责序列化 getData 通过直接将结果流式传输到响应 OutputStream

Spring will take care of serializing getData by streaming the results directly to the response OutputStream.

这不会自行运行。例如,您用来访问服务的网址

This won't work on its own. For one, the url you are using to access your service

/dynamicScenario.htm

导致 Spring的内容协商开始。它看到 .htm 并认为你期待 text / html 内容。关闭内容协商或使用不带扩展名的网址

is causing Spring's content negotiation to kick in. It sees .htm and thinks you are expecting text/html content. Either turn off content negotiation or use a URL without an extension

/dynamicScenario

然后,Spring将查看 Accept 标题,以确定客户期望的内容。因为那是

Spring will then, instead, look at the Accept headers for figuring out what your client is expecting. Since that is

dataType: "json"

它将使用 MappingJackson2HttpMessageConverter 编写 application / json

以下是一些需要解决的问题

Here are some more things to fix

AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(ApplicationConfig.class);

servletContext.addListener(new ContextLoaderListener(context));

ServletRegistration.Dynamic servletRegistration = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
servletRegistration.setLoadOnStartup(1);
servletRegistration.addMapping("*.htmlx");

您目前正在制作 ContextLoaderListener DispatcherServlet 加载相同的 ApplicationContext 。这是不必要的,可能有害。 DispatcherServlet 已经使用 ContextLoaderListener 上下文作为父上下文。你可以在这里阅读更多相关信息:

You're currently making both the ContextLoaderListener and DispatcherServlet load the same ApplicationContext. This is unnecessary and potentially harmful. The DispatcherServlet already uses the ContextLoaderListener context as a parent context. You can read more about it here:

  • What is the difference between ApplicationContext and WebApplicationContext in Spring MVC?

如果您的 ApplicationConfig 仅包含MVC配置元素,则仅加载 DispatcherServlet 。您不需要 ContextLoaderListener

If your ApplicationConfig only contains MVC configuration elements, only have the DispatcherServlet load it. You won't need a ContextLoaderListener.

如果它有与MVC堆栈无关的其他类型的配置元素,请将其拆分为两个 @Configuration 类。将MVC一个传递给 DispatcherServlet ,另一个传递给 ContextLoaderListener

If it has other types of config elements unrelated to the MVC stack, split it into two @Configuration classes. Pass the MVC one to the DispatcherServlet and the other to the ContextLoaderListener.

不要将JSON作为表单参数传递。将其直接传递到请求正文并使用 @RequestBody 将其直接反序列化为您期望的类型。

Don't pass JSON as form parameters. Pass it directly into the request body and use @RequestBody to deserialize it directly into your expected type.

@RequestMapping(method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> getDynamicScenarioData(@RequestBody Map<String,Object> queryParameters) throws JsonParseException, JsonMappingException, IOException
 {
     Map<String, Object> getData = service.runDynamicScenario(queryParameters, /* find a better way to pass this map.get("queryString") */);

     return getData;
}

您自己保存 ObjectMapper 每个请求上的对象(它是一个繁重的对象),你让Spring负责所有的反序列化(再次流式传输)。你必须找到另一种传递 queryString 的方法(你仍然可以将它作为单个查询参数传递并通过`@RequestParam获取)。

You save yourself an ObjectMapper object on each request (it's a heavy object) and you let Spring take care of all the deserializing (again streaming). You'll have to find another way to pass the queryString (you can still pass it as a single query parameter and get it with `@RequestParam).

这篇关于将JSON直接流式传输到与Jackson的响应的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆