【问题标题】:Streaming JSON directly to response with Jackson直接流式传输 JSON 以响应 Jackson
【发布时间】:2015-10-07 06:52:14
【问题描述】:

目前,我需要向 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");
  }
}

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

【问题讨论】:

  • 你为什么不让你的方法返回类型Map&lt;String, Object&gt;并返回getData? Jackson 应该能够在序列化的同时进行流式传输。
  • 我已经尝试过了,但我得到的状态代码:406 Not Acceptable in response..
  • 您在请求中发送了什么 Accept 标头?
  • 我已经粘贴了我的 ajax..我的数据类型是“json”所以它应该可以工作..
  • 向我们展示您的 MVC 配置。另外,不要将 JSON 作为表单参数发送。直接在请求正文中发送并使用@RequestBody 让Spring 将其直接反序列化为Map&lt;String,Object&gt;

标签: java ajax json spring-mvc jackson


【解决方案1】:

你要解决的问题是

return writer.writeValueAsString(getData); 

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

有几种方法可以处理这个问题。最简单的就是把你的返回类型改成Map&lt;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's content negotiation 启动。它看到.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 获取)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-09-15
    • 2016-02-16
    • 2018-02-19
    • 2016-12-11
    • 1970-01-01
    • 1970-01-01
    • 2018-03-10
    • 1970-01-01
    相关资源
    最近更新 更多