直接将JSON流式传输到杰克逊

时间:2015-07-17 14:04:40

标签: java ajax json spring-mvc jackson

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

   @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是:

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

我的DispatcherServlet设置:

           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对象。有任何想法吗?感谢

1 个答案:

答案 0 :(得分:5)

您要解决的问题是

return writer.writeValueAsString(getData); 

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

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

@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

这不靠自己的工作。例如,您用来访问服务的网址

/dynamicScenario.htm

导致Spring's content negotiation启动。它看到.htm并认为您期待text/html内容。要么关闭内容协商,要么使用不带扩展名的网址

/dynamicScenario

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

dataType: "json"

它将使用application/json撰写MappingJackson2HttpMessageConverter

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

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

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

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

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

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

@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获取)。