Qui définit le type de contenu de réponse dans Spring MVC (@ResponseBody)

126

J'ai dans mon application Web Spring MVC Java pilotée par Annotation exécutée sur le serveur Web de la jetée (actuellement dans le plugin maven jetty).

J'essaie de prendre en charge AJAX avec une méthode de contrôleur renvoyant uniquement le texte d'aide String. Les ressources sont en codage UTF-8, tout comme la chaîne, mais ma réponse du serveur est fournie avec

content-encoding: text/plain;charset=ISO-8859-1 

même quand mon navigateur envoie

Accept-Charset  windows-1250,utf-8;q=0.7,*;q=0.7

J'utilise en quelque sorte la configuration par défaut du printemps

J'ai trouvé un indice pour ajouter ce bean à la configuration, mais je pense qu'il n'est tout simplement pas utilisé, car il indique qu'il ne prend pas en charge l'encodage et qu'un par défaut est utilisé à la place.

<bean class="org.springframework.http.converter.StringHttpMessageConverter">
    <property name="supportedMediaTypes" value="text/plain;charset=UTF-8" />
</bean>

Mon code de contrôleur est (notez que ce changement de type de réponse ne fonctionne pas pour moi):

@RequestMapping(value = "ajax/gethelp")
public @ResponseBody String handleGetHelp(Locale loc, String code, HttpServletResponse response) {
    log.debug("Getting help for code: " + code);
    response.setContentType("text/plain;charset=UTF-8");
    String help = messageSource.getMessage(code, null, loc);
    log.debug("Help is: " + help);
    return help;
}
Hurda
la source

Réponses:

59

Une simple déclaration du StringHttpMessageConverterbean ne suffit pas, vous devez l'injecter dans AnnotationMethodHandlerAdapter:

<bean class = "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <array>
            <bean class = "org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes" value = "text/plain;charset=UTF-8" />
            </bean>
        </array>
    </property>
</bean>

Cependant, en utilisant cette méthode, vous devez redéfinir tous les HttpMessageConverters, et cela ne fonctionne pas non plus avec <mvc:annotation-driven />.

Donc, peut-être la méthode la plus pratique mais la plus laide est d'intercepter l'instanciation de AnnotationMethodHandlerAdapteravec BeanPostProcessor:

public class EncodingPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String name)
            throws BeansException {
        if (bean instanceof AnnotationMethodHandlerAdapter) {
            HttpMessageConverter<?>[] convs = ((AnnotationMethodHandlerAdapter) bean).getMessageConverters();
            for (HttpMessageConverter<?> conv: convs) {
                if (conv instanceof StringHttpMessageConverter) {
                    ((StringHttpMessageConverter) conv).setSupportedMediaTypes(
                        Arrays.asList(new MediaType("text", "html", 
                            Charset.forName("UTF-8"))));
                }
            }
        }
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String name)
            throws BeansException {
        return bean;
    }
}

-

<bean class = "EncodingPostProcessor " />
axtavt
la source
10
Cela ressemble à un sale hack. Je n'aime pas ça mais utiliser. Les développeurs de framework Spring devraient travailler sur ce cas!
digz6666
Où va la ligne <bean class = "EncodingPostProcessor" />?
zod
1
@zod: In DispatcherServlet's config ( ...-servlet.xml)
axtavt
Merci. Cela semble ignoré. Nous utilisons mvc (je pense) et nous avons une classe avec un attribut @Controller, qui semble être le point d'entrée. La classe n'est mentionnée nulle part ailleurs (elle a une interface avec un nom similaire) mais elle est instanciée et appelée correctement. Les chemins sont mappés avec un attribut @RequestMapping. Nous ne pouvons pas contrôler le type de contenu de la réponse (nous avons besoin de xml). Comme vous pouvez probablement le constater, je n'ai aucune idée de ce que je fais et le développeur qui l'a créé a quitté mon entreprise. Merci.
zod
3
Comme le dit @ digz6666, c'est un sale hack. Spring devrait voir comment JAX-RS le fait.
Adam Gent
166

J'ai trouvé une solution pour Spring 3.1. avec l'utilisation de l'annotation @ResponseBody. Voici un exemple de contrôleur utilisant la sortie Json:

@RequestMapping(value = "/getDealers", method = RequestMethod.GET, 
produces = "application/json; charset=utf-8")
@ResponseBody
public String sendMobileData() {

}
guerrier
la source
7
+1. Cela l'a résolu pour moi aussi, mais seulement après que je suis passé à l'utilisation <mvc:annotation-driven/>dans applicationContext. (Au lieu de <bean class=" [...] DefaultAnnotationHandlerMapping"/>, qui est de toute façon obsolète au printemps 3.2 ...)
Jonik
Est-ce que cela produit application / xml s'il est annoté de cette façon?
Hurda
2
@Hurda: Vous pouvez évidemment spécifier n'importe quel type de contenu que vous souhaitez en changeant la valeur de l' producesattribut.
Jonik
1
Il existe également un MediaType.APPLICATION_JSON_VALUE pour "application / json".
dev
2
Pour UTF-8, voir MediaType.APPLICATION_JSON_UTF8_VALUE.
calvinf
51

Notez que dans Spring MVC 3.1, vous pouvez utiliser l'espace de noms MVC pour configurer les convertisseurs de messages:

<mvc:annotation-driven>
  <mvc:message-converters register-defaults="true">
    <bean class="org.springframework.http.converter.StringHttpMessageConverter">
      <property name="supportedMediaTypes" value = "text/plain;charset=UTF-8" />
    </bean>
  </mvc:message-converters>
</mvc:annotation-driven>

Ou configuration basée sur le code:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

  private static final Charset UTF8 = Charset.forName("UTF-8");

  @Override
  public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
    stringConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "plain", UTF8)));
    converters.add(stringConverter);

    // Add other converters ...
  }
}
Rossen Stoyanchev
la source
Sorte de travaux, sauf que 1) il pollue la réponse avec un en- Accept-Charsettête qui répertorie probablement tous les encodages de caractères connus, et 2) lorsque la requête a un en- Accepttête la supportedMediaTypespropriété du convertisseur n'est pas utilisée , donc par exemple lorsque je fais la requête en tapant directement l'URL dans un navigateur, la réponse a un en- Content-Type: text/htmltête à la place.
Giulio Piancastelli
3
Vous pouvez simplifier car "text / plain" est par défaut de toute façon: <bean class="org.springframework.http.converter.StringHttpMessageConverter"><constructor-arg value="UTF-8" /></bean>
Igor Mukhin
Cette réponse doit être acceptée comme la bonne réponse. De plus, la façon dont @IgorMukhin définit le bean StringHttpMessageConverter fonctionne. Cette réponse est utilisée pour définir les types de contenu de réponse pour tous les servlets. Si vous avez juste besoin de définir le type de contenu de réponse pour une méthode de contrôleur particulière, utilisez plutôt la réponse de Warrior (utilisez produit un argument dans @RequestMapping)
PickBoy
3
@GiulioPiancastelli votre première question peut être résolue en ajoutant <property name = "writeAcceptCharset" value = "false" /> au bean
PickBoy
44

Juste au cas où vous pouvez également définir l'encodage de la manière suivante:

@RequestMapping(value = "ajax/gethelp")
public ResponseEntity<String> handleGetHelp(Locale loc, String code, HttpServletResponse response) {
    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.add("Content-Type", "text/html; charset=utf-8");

    log.debug("Getting help for code: " + code);
    String help = messageSource.getMessage(code, null, loc);
    log.debug("Help is: " + help);

    return new ResponseEntity<String>("returning: " + help, responseHeaders, HttpStatus.CREATED);
}

Je pense que l'utilisation de StringHttpMessageConverter est meilleure que cela.

digz6666
la source
C'est aussi la solution si vous obtenez l'erreur the manifest may not be valid or the file could not be opened.dans IE 11. Merci digz!
Arun Christopher
21

vous pouvez ajouter produit = "text / plain; charset = UTF-8" à RequestMapping

@RequestMapping(value = "/rest/create/document", produces = "text/plain;charset=UTF-8")
@ResponseBody
public String create(Document document, HttpServletRespone respone) throws UnsupportedEncodingException {

    Document newDocument = DocumentService.create(Document);

    return jsonSerializer.serialize(newDocument);
}

voir ce blog pour plus de détails

Charlie Wu
la source
2
Ce code ne se compilerait pas; vous retournez quelque chose d'une méthode void.
Andrew Swan
2
désolé mauvais bug, c'est corrigé maintenant
Charlie Wu
3
C'est une réponse incorrecte. Selon les documents de printemps: les types de médias productibles de la demande mappée, ce qui réduit le mappage principal. Le format est une séquence de types de supports ("text / plain", "application / *), avec une requête mappée uniquement si Accept correspond à l'un de ces types de supports. Les expressions peuvent être annulées en utilisant l'opérateur"! ", Comme dans "! text / plain", qui correspond à toutes les demandes avec un Accept autre que "text / plain".
Oleksandr_DJ
@CharlieWu Il y a un problème avec le lien
Matt
10

Je combattais ce problème récemment et j'ai trouvé une bien meilleure réponse disponible au printemps 3.1:

@RequestMapping(value = "ajax/gethelp", produces = "text/plain")

Donc, aussi simple que JAX-RS, comme tous les commentaires l'ont indiqué, cela pourrait / devrait être.

dbyoung
la source
Cela vaut la peine d'être porté sur Spring 3.1 pour!
young.fu.panda
5
@dbyoung Cela ne semble pas correct, le javadoc pour le producesdit: "... la requête n'est mappée que si le Content-Type correspond à l'un de ces types de média." ce qui signifie AFAIK que le producesest pertinent pour savoir si la méthode correspond à une demande et non comment quel type de contenu la réponse devrait avoir.
Ittai
@Ittai correct! "produit" détermine si la méthode correspond à la demande, mais PAS le type de contenu dans la réponse. quelque chose d'autre doit être à la recherche de "produit" pour déterminer le type de contenu à définir
anton1980
6

Vous pouvez utiliser produits pour indiquer le type de réponse que vous envoyez depuis le contrôleur. Ce mot-clé "produit" sera plus utile dans la requête ajax et a été très utile dans mon projet

@RequestMapping(value = "/aURLMapping.htm", method = RequestMethod.GET, produces = "text/html; charset=utf-8") 

public @ResponseBody String getMobileData() {

}
Balasubramanian Jayaraman
la source
4

Merci digz6666, votre solution fonctionne pour moi avec de légères modifications car j'utilise json:

responseHeaders.add ("Content-Type", "application / json; charset = utf-8");

La réponse donnée par axtavt (que vous avez recommandé) ne fonctionnera pas pour moi. Même si j'ai ajouté le bon type de média:

if (conv instanceof StringHttpMessageConverter) {                   
                    ((StringHttpMessageConverter) conv) .setSupportedMediaTypes (
                        Arrays.asList (
                                nouveau MediaType ("text", "html", Charset.forName ("UTF-8")),
                                nouveau MediaType ("application", "json", Charset.forName ("UTF-8"))));
                }
redochka
la source
4

J'ai défini le type de contenu dans MarshallingView dans le bean ContentNegotifyingViewResolver . Cela fonctionne facilement, propre et en douceur:

<property name="defaultViews">
  <list>
    <bean class="org.springframework.web.servlet.view.xml.MarshallingView">
      <constructor-arg>
        <bean class="org.springframework.oxm.xstream.XStreamMarshaller" />     
      </constructor-arg>
      <property name="contentType" value="application/xml;charset=UTF-8" />
    </bean>
  </list>
</property>
Reto-san
la source
3

J'utilise le CharacterEncodingFilter, configuré dans web.xml. Peut-être que cela aide.

    <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
Theresia Sofia Snow
la source
1
Cela ne fait que filtrer le caractère dans la demande, pas en réponse - j'utilise déjà celui-ci
Hurda
@Hurda: Avec forceEncoding=trueça filtre aussi la réponse, mais ça n'aiderait pas dans ce cas.
axtavt le
Réponse la meilleure et la plus rapide à ce jour. J'étais aussi déjà en train de déclarer et d'utiliser ce filtre, mais avec forceEncoding=false. Je viens de le définir falseet "charset = UTF-8" est ajouté avec succès à l'en- Content-Typetête.
Saad Benbouzid
2

si rien de ce qui précède n'a fonctionné pour vous, essayez de faire des requêtes ajax sur "POST" et non sur "GET", cela a bien fonctionné pour moi ... rien de ce qui précède n'a fonctionné. J'ai aussi le characterEncodingFilter.

Marius
la source
2
package com.your.package.spring.fix;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

/**
 * @author Szilard_Jakab (JaKi)
 * Workaround for Spring 3 @ResponseBody issue - get incorrectly 
   encoded parameters     from the URL (in example @ JSON response)
 * Tested @ Spring 3.0.4
 */
public class RepairWrongUrlParamEncoding {
    private static String restoredParamToOriginal;

    /**
    * @param wrongUrlParam
    * @return Repaired url param (UTF-8 encoded)
    * @throws UnsupportedEncodingException
    */
    public static String repair(String wrongUrlParam) throws 
                                            UnsupportedEncodingException {
    /* First step: encode the incorrectly converted UTF-8 strings back to 
                  the original URL format
    */
    restoredParamToOriginal = URLEncoder.encode(wrongUrlParam, "ISO-8859-1");

    /* Second step: decode to UTF-8 again from the original one
    */
    return URLDecoder.decode(restoredParamToOriginal, "UTF-8");
    }
}

Après avoir essayé beaucoup de solutions de contournement pour ce problème ... J'ai pensé à cela et cela fonctionne bien.

Szilard Jakab
la source
2

Le moyen simple de résoudre ce problème dans Spring 3.1.1 est le suivant: ajoutez les codes de configuration suivants dans servlet-context.xml

    <annotation-driven>
    <message-converters register-defaults="true">
    <beans:bean class="org.springframework.http.converter.StringHttpMessageConverter">
    <beans:property name="supportedMediaTypes">    
    <beans:value>text/plain;charset=UTF-8</beans:value>
    </beans:property>
    </beans:bean>
    </message-converters>
    </annotation-driven>

Pas besoin de remplacer ou de mettre en œuvre quoi que ce soit.

AdaroMu
la source
2

si vous décidez de résoudre ce problème via la configuration suivante:

<mvc:annotation-driven>
  <mvc:message-converters register-defaults="true">
    <bean class="org.springframework.http.converter.StringHttpMessageConverter">
      <property name="supportedMediaTypes" value = "text/plain;charset=UTF-8" />
    </bean>
  </mvc:message-converters>
</mvc:annotation-driven>

vous devez confirmer qu'il ne devrait y avoir qu'une seule balise mvc: basée sur les annotations dans tout votre fichier * .xml. sinon, la configuration risque de ne pas être efficace.

Liche
la source
1

Selon le lien "Si un codage de caractères n'est pas spécifié, la spécification de servlet exige qu'un codage ISO-8859-1 soit utilisé". Si vous utilisez le printemps 3.1 ou une version ultérieure, utilisez la configuration de jachère pour définir charset = UTF-8 sur corps de la réponse
@RequestMapping (valeur = "votre url de mappage", produit = "text / plain; charset = UTF-8")

Ramesh Papaganti
la source
0
public final class ConfigurableStringHttpMessageConverter extends AbstractHttpMessageConverter<String> {

    private Charset defaultCharset;

    public Charset getDefaultCharset() {
        return defaultCharset;
    }

    private final List<Charset> availableCharsets;

    private boolean writeAcceptCharset = true;

    public ConfigurableStringHttpMessageConverter() {
        super(new MediaType("text", "plain", StringHttpMessageConverter.DEFAULT_CHARSET), MediaType.ALL);
        defaultCharset = StringHttpMessageConverter.DEFAULT_CHARSET;
        this.availableCharsets = new ArrayList<Charset>(Charset.availableCharsets().values());
    }

    public ConfigurableStringHttpMessageConverter(String charsetName) {
        super(new MediaType("text", "plain", Charset.forName(charsetName)), MediaType.ALL);
        defaultCharset = Charset.forName(charsetName);
        this.availableCharsets = new ArrayList<Charset>(Charset.availableCharsets().values());
    }

    /**
     * Indicates whether the {@code Accept-Charset} should be written to any outgoing request.
     * <p>Default is {@code true}.
     */
    public void setWriteAcceptCharset(boolean writeAcceptCharset) {
        this.writeAcceptCharset = writeAcceptCharset;
    }

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

    @Override
    protected String readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException {
        Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
        return FileCopyUtils.copyToString(new InputStreamReader(inputMessage.getBody(), charset));
    }

    @Override
    protected Long getContentLength(String s, MediaType contentType) {
        Charset charset = getContentTypeCharset(contentType);
        try {
            return (long) s.getBytes(charset.name()).length;
        }
        catch (UnsupportedEncodingException ex) {
            // should not occur
            throw new InternalError(ex.getMessage());
        }
    }

    @Override
    protected void writeInternal(String s, HttpOutputMessage outputMessage) throws IOException {
        if (writeAcceptCharset) {
            outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
        }
        Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
        FileCopyUtils.copy(s, new OutputStreamWriter(outputMessage.getBody(), charset));
    }

    /**
     * Return the list of supported {@link Charset}.
     *
     * <p>By default, returns {@link Charset#availableCharsets()}. Can be overridden in subclasses.
     *
     * @return the list of accepted charsets
     */
    protected List<Charset> getAcceptedCharsets() {
        return this.availableCharsets;
    }

    private Charset getContentTypeCharset(MediaType contentType) {
        if (contentType != null && contentType.getCharSet() != null) {
            return contentType.getCharSet();
        }
        else {
            return defaultCharset;
        }
    }
}

Exemple de configuration:

    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="messageConverters">
            <util:list>
                <bean class="ru.dz.mvk.util.ConfigurableStringHttpMessageConverter">
                    <constructor-arg index="0" value="UTF-8"/>
                </bean>
            </util:list>
        </property>
    </bean>
Igor Kostomin
la source