目前,我需要将一个大型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对象。有任何想法吗?感谢
答案 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");
您目前正在使ContextLoaderListener
和DispatcherServlet
加载相同的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获取)。