繁体   English   中英

直接流式传输JSON以响应Jackson

[英]Streaming JSON directly to response with Jackson

当前,我需要向ajax请求发送一个大型json对象。 为此,我使用了下面的工作正常的控制器方法。

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

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

您要解决的问题是

return writer.writeValueAsString(getData); 

创建太大的String并导致OutOfMemoryError Jackson支持Streaming API ,Spring在其MappingJackson2HttpMessageConverter使用了该API ,该API在响应正文(和请求正文)中处理将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将通过将结果直接流式传输到响应OutputStream来对getData进行序列化。

这将无法单独工作。 例如,您用来访问服务的网址

/dynamicScenario.htm

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

/dynamicScenario

然后,Spring将查看Accept标头,以弄清客户的期望。 因为那是

dataType: "json"

它将使用MappingJackson2HttpMessageConverter编写application/json


这里还有一些要解决的问题

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

暂无
暂无

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

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