在 Spring MVC 3.1 控制器的处理程序方法中直接流到响应输出流

Posted

技术标签:

【中文标题】在 Spring MVC 3.1 控制器的处理程序方法中直接流到响应输出流【英文标题】:Stream directly to response output stream in handler method of Spring MVC 3.1 controller 【发布时间】:2013-03-07 22:50:34 【问题描述】:

我有一个控制器方法来处理 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 - 这是一个公平的问题。我想我的印象是,尽管有可能,但这不是正确的做事方式。 @Ralph 在this question 中有一个答案,说它使测试变得更加困难。在我编写很多执行此操作的处理程序方法之前,我想知道是否还有其他方法。到目前为止,我似乎必须编写一个自定义 HttpMessageConverter 【参考方案1】:

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

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

@见Spring Reference Documentation 3.1 Chapter 16.3.3.1 Supported method argument types

附言我觉得使用OutputStreamWriter 作为参数在测试中仍然比HttpServletResponse 更容易使用——感谢关注what I have written ;-)

【讨论】:

感谢您的回答。传递OutputStreamWriter 优于传递HttpServletResponse【参考方案2】:

最后,我为此写了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 对象。顺序很重要。

【讨论】:

感谢您抽出宝贵时间用更新的答案回复您自己的帖子,恕我直言,这是最好的答案。 @SteveMARION - 不客气。看起来我是唯一一个愿意这样做的人。我认为这个答案不会获得与接受的答案一样多的选票,但我认为它会更接近。【参考方案3】:

请注意,如果您使用 OutputStream 或 Writer,则需要您自己编写标头。

一种解决方法是使用 InputStreamResource/ResourceHttpMessageConverter

【讨论】:

以上是关于在 Spring MVC 3.1 控制器的处理程序方法中直接流到响应输出流的主要内容,如果未能解决你的问题,请参考以下文章

spring mvc 是啥

Spring MVC-处理程序映射(Handler Mapping)-控制器类名称处理程序映射(Controller Class Name Handler Mapping)示例(转载实践)

如何在Spring MVC控制器类中处理两个jsp表单页面

Spring MVC 3.1 中的基本身份验证

Spring MVC 详解

Spring MVC框架 学习笔记总结