在Spring MVC 3.1控制器的处理方法中直接将流式数据输出到响应输出流

24

我有一个控制器方法,处理ajax调用并返回JSON。我正在使用来自json.org的JSON库创建JSON。

我可以执行以下操作:

@RequestMapping(method = RequestMethod.POST)
@ResponseBody
public String getJson()
{
    JSONObject rootJson = new JSONObject();

    // Populate JSON

    return rootJson.toString();
}

但是将JSON字符串组合起来,然后让Spring将其写入响应的输出流中是低效的。

相反,我可以像这样直接将其写入响应的输出流中:

@RequestMapping(method = RequestMethod.POST)
public void getJson(HttpServletResponse response)
{
    JSONObject rootJson = new JSONObject();

    // Populate JSON

    rootJson.write(response.getWriter());
}

但似乎有更好的方法来处理这个问题,而不是不得不将 HttpServletResponse 传递到处理程序方法中。

是否有另一个类或接口可以从处理程序方法中返回,并与 @ResponseBody 注释一起使用?


将 HttpServletResponse 传递到处理程序方法中有什么问题? - arahant
2
@arahant - 这是一个公正的问题。我认为,虽然这是可能的,但这不是正确的做法。在这个问题中,@Ralph给出了一个答案,说这会使测试变得更加困难。在编写许多处理程序方法之前,我想找出是否有另一种方法。到目前为止,看起来我必须编写一个自定义的HttpMessageConverter - John S
3个回答

32

你可以将输出流或写入器作为控制器方法的参数。

@RequestMapping(method = RequestMethod.POST)
public void getJson(Writer responseWriter) {
    JSONObject rootJson = new JSONObject();
    rootJson.write(responseWriter);
}

@see Spring 参考文档 3.1 章节 16.3.3.1 支持的方法参数类型

p.s. 我认为在测试中使用 OutputStreamWriter 作为参数仍然比使用 HttpServletResponse 更容易使用 - 并感谢您关注我所写的内容 ;-)


谢谢你的回答。传递一个 OutputStream 或者一个 Writer 要比传递 HttpServletResponse 更可取。 - John S

4
最终,我为此编写了一个HttpMessageConverter,使用它,我可以完成以下操作:
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
public JSONObject getJson()
    throws JSONException
{
    JSONObject rootJson = new JSONObject();

    // Populate JSON

    return rootJson;
}

这是我的HttpMessageConverter类:

package com.example;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

public class JsonObjectHttpMessageConverter
    extends AbstractHttpMessageConverter<JSONObject>
{
    private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    public JsonObjectHttpMessageConverter()
    {
        super(new MediaType("application", "json"), new MediaType("text", "javascript"));
    }

    @Override
    protected boolean supports(Class<?> clazz)
    {
        return JSONObject.class.equals(clazz);
    }

    @Override
    protected JSONObject readInternal(Class<? extends JSONObject> clazz,
                                      HttpInputMessage            inputMessage)
        throws IOException,
               HttpMessageNotReadableException
    {
        throw new UnsupportedOperationException();
    }

    @Override
    protected void writeInternal(JSONObject        jsonObject,
                                 HttpOutputMessage outputMessage)
        throws IOException,
               HttpMessageNotWritableException
    {
        PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputMessage.getBody(),
                                                                    getContentTypeCharset(outputMessage)));

        try
        {
            jsonObject.write(writer);
            writer.flush();
        }
        catch (JSONException e)
        {
            throw new HttpMessageNotWritableException(e.getMessage(), e);
        }
    }

    private Charset getContentTypeCharset(HttpMessage message)
    {
        MediaType contentType = message.getHeaders().getContentType();

        Charset charset = (contentType != null) ? contentType.getCharSet() : null;

        return (charset != null) ? charset : DEFAULT_CHARSET;
    }
}
HttpMessageConverter 必须在 Spring 中注册。可以在 dispatcher-servlet.xml 文件中完成如下操作:
<beans ...>

    ...    

    <mvc:annotation-driven conversion-service="conversionService" validator="validator">
        <mvc:argument-resolvers>
            ...
        </mvc:argument-resolvers>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/plain;charset=UTF-8</value>
                        <value>application/json;charset=UTF-8</value>
                        <value>*/*</value>
                    </list>
                </property>
                <property name="writeAcceptCharset" value="false" />
            </bean>
            <bean class="com.example.JsonObjectHttpMessageConverter" />
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
                <property name="objectMapper" ref="jacksonObjectMapper" />
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    ...

</beans>

正如您所看到的,我还注册了其他HttpMessageConverter对象。顺序很重要。


感谢您抽出时间更新自己的帖子并提供了最佳答案,我认为这是非常好的。 - Steve Marion
@SteveMARION - 不用谢。看起来只有我会费心去做这件事。我想这个答案不会得到像被接受的答案那么多的投票,但我认为它更接近正确。 - John S

1
请注意,如果您使用OutputStream或Writer,则需要自己编写标头。
一个解决方法是使用InputStreamResource/ResourceHttpMessageConverter。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接