web-dev-qa-db-fra.com

Diffuser directement vers le flux de sortie de réponse dans la méthode de gestion du contrôleur Spring MVC 3.1

J'ai une méthode de contrôleur qui gère les appels ajax et retourne JSON. J'utilise la bibliothèque JSON de json.org pour créer le JSON.

Je pourrais faire ce qui suit:

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

    // Populate JSON

    return rootJson.toString();
}

Mais il est inefficace de rassembler la chaîne JSON, seulement pour que Spring l'écrive dans le flux de sortie de la réponse.

Au lieu de cela, je peux l'écrire directement dans le flux de sortie de réponse comme ceci:

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

    // Populate JSON

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

Mais il semble qu'il y aurait une meilleure façon de procéder que d'avoir à passer le HttpServletResponse dans la méthode du gestionnaire.

Y a-t-il une autre classe ou interface qui peut être renvoyée par la méthode du gestionnaire que je peux utiliser, avec le @ResponseBody annotation?

21
John S

Vous pouvez avoir le flux de sortie ou l'enregistreur comme paramètre de votre méthode de contrôleur.

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

@see Spring Reference Documentation 3.1 Chapitre 16.3.3.1 Types d'arguments de méthode pris en charge

p.s. Je pense que l'utilisation de OutputStream ou Writer comme paramètre est encore plus facile à utiliser dans les tests qu'un HttpServletResponse - et merci de faire attention à ce que je ont écrit ;-)

31
Ralph

Au final, j'ai écrit un HttpMessageConverter pour cela. Avec lui, je peux faire ce qui suit:

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

    // Populate JSON

    return rootJson;
}

Voici ma classe 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 doit être enregistré auprès de Spring. Cela peut être fait dans le dispatcher-servlet.xml fichier comme celui-ci:

<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>

Comme vous pouvez le voir, j'ai également enregistré d'autres objets HttpMessageConverter. L'ordre est important.

4
John S

Notez que si vous utilisez OutputStream ou Writer, vous devez écrire les en-têtes vous-même.

Une solution de contournement consiste à utiliser InputStreamResource/ResourceHttpMessageConverter

1
Hadrien