将JSON直接流式传输到与Jackson的响应 [英] Streaming JSON directly to response with 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屋!