Quem define o tipo de conteúdo da resposta no Spring MVC (@ResponseBody)

126

Estou tendo no meu aplicativo da web Spring MVC Java acionado por anotações executado no servidor da web jetty (atualmente no plugin maven jetty).

Estou tentando fazer algum suporte AJAX com um método de controlador retornando apenas o texto de ajuda da String. Os recursos estão na codificação UTF-8 e a string também, mas minha resposta do servidor vem com

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

mesmo quando meu navegador envia

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

Eu estou usando alguma configuração padrão de primavera

Encontrei uma dica para adicionar esse bean à configuração, mas acho que ele não é usado, porque diz que não suporta a codificação e que é usada uma padrão.

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

Meu código de controlador é (observe que essa alteração no tipo de resposta não está funcionando para mim):

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

Respostas:

59

Simples declaração do StringHttpMessageConverterbean não é suficiente, você precisa injetá-lo em 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>

No entanto, usando esse método, você precisa redefinir todos os HttpMessageConverters e também não funciona <mvc:annotation-driven />.

Portanto, talvez o método mais conveniente, porém feio, seja interceptar a instanciação do AnnotationMethodHandlerAdaptercom 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
fonte
10
Parece um truque sujo. Eu não gosto disso, mas usar. Os desenvolvedores do framework Spring devem trabalhar nesse caso!
digz6666
Para onde vai a linha <bean class = "EncodingPostProcessor" />?
Zod 30/03/11
1
@zod: In DispatcherServlet's config ( ...-servlet.xml)
axtavt 30/03
Obrigado. Parece ser ignorado. Estamos usando mvc (acho) e temos uma classe com um atributo @Controller, que parece ser o ponto de entrada. A classe não é mencionada em nenhum outro lugar (possui uma interface com um nome semelhante), mas é instanciada e chamada corretamente. Os caminhos são mapeados com um atributo @RequestMapping. Não podemos controlar o tipo de conteúdo da resposta (precisamos de xml). Como você provavelmente pode perceber, não tenho ideia do que estou fazendo, e o desenvolvedor que criou isso deixou minha empresa. Obrigado.
Zod 30/03/11
3
Como @ digz6666 diz que este é um truque sujo. A primavera deve ver como o JAX-RS faz isso.
Adam Gent
166

Encontrei solução para o Spring 3.1. com o uso da anotação @ResponseBody. Aqui está um exemplo de controlador usando saída Json:

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

}
Guerreiro
fonte
7
+1. Isso também resolveu para mim, mas somente depois que eu mudei para o uso <mvc:annotation-driven/>no applicationContext. (Em vez de <bean class=" [...] DefaultAnnotationHandlerMapping"/>, que está obsoleta na Primavera 3.2 de qualquer maneira ...)
Jonik
ca isso produz application / xml se anotado dessa maneira?
Hurda 27/05
2
@Hurda: Obviamente, você pode especificar qualquer tipo de conteúdo que desejar, alterando o valor do producesatributo.
28613 Jonik
1
Existe um MediaType.APPLICATION_JSON_VALUE, para "application / json" também.
Dev
2
Para UTF-8, consulte MediaType.APPLICATION_JSON_UTF8_VALUE.
Calvinf
51

Observe que no Spring MVC 3.1 você pode usar o namespace MVC para configurar conversores de mensagens:

<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 configuração baseada em código:

@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
fonte
Tipo de trabalhos, exceto que 1) polui a resposta com um Accept-Charsetcabeçalho que provavelmente lista todas as codificações de caracteres conhecidas e 2) quando a solicitação possui um Acceptcabeçalho, a supportedMediaTypespropriedade do conversor não é usada , por exemplo, quando faço a digitação da solicitação diretamente a URL em um navegador, a resposta possui um Content-Type: text/htmlcabeçalho.
Giulio Piancastelli 26/02
3
Você pode simplificar como "text / plain" é padrão de qualquer maneira: <bean class="org.springframework.http.converter.StringHttpMessageConverter"><constructor-arg value="UTF-8" /></bean>
Igor Mukhin
Esta resposta deve ser aceita como a resposta certa. Além disso, a maneira de @IgorMukhin definir o bean StringHttpMessageConverter funciona. Esta resposta é usada para definir os tipos de conteúdo de resposta para todos os servlets. Se você só precisa definir reponse tipo de conteúdo para um método de tratamento particular, a resposta de uso Guerreiro vez (uso produz argumento em @RequestMapping)
PickBoy
3
@ GiulioPiancastelli, sua primeira pergunta pode ser resolvida adicionando <nome da propriedade = "writeAcceptCharset" value = "false" /> ao bean
PickBoy 23/03/16
44

Caso você também possa definir a codificação da seguinte maneira:

@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);
}

Eu acho que usar StringHttpMessageConverter é melhor que isso.

digz6666
fonte
Esta também é a solução se você receber o erro the manifest may not be valid or the file could not be opened.no IE 11. Obrigado digz!
Arun Christopher
21

você pode adicionar produz = "text / plain; charset = UTF-8" ao 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);
}

veja este blog para mais detalhes

Charlie Wu
fonte
2
Esse código não seria compilado; você está retornando algo de um método nulo.
Andrew Swan
2
desculpe mau erro, ele é fixo agora
Charlie Wu
3
É uma resposta incorreta. Conforme os documentos da primavera: Os tipos de mídia produzíveis da solicitação mapeada, restringindo o mapeamento primário. O formato é uma sequência de tipos de mídia ("text / plain", "application / *), com uma solicitação mapeada apenas se o Accept corresponder a um desses tipos de mídia. As expressões podem ser negadas usando o operador"! ", Como em "! text / plain", que corresponde a todas as solicitações com um Accept diferente de "text / plain". #
Oleksandr_DJ
@CharlieWu Há um problema com o link #
16/07 Matt
10

Eu estava enfrentando esse problema recentemente e encontrei uma resposta muito melhor disponível no Spring 3.1:

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

Portanto, tão fácil quanto o JAX-RS, como todos os comentários indicaram, poderia / deveria ser.

dbyoung
fonte
Vale a pena a Primavera 3.1 para!
Young.fu.panda
5
@dbyoung Isso não parece certo, o javadoc producesdiz: "... solicitação mapeada apenas se o Tipo de conteúdo corresponder a um desses tipos de mídia". o que significa que o AFAIK producesé relevante para saber se o método corresponde a uma solicitação e não como que tipo de conteúdo a resposta deve ter.
Ittai
@Ittai correct! "produz" determina se o método corresponde à solicitação, mas NÃO que tipo de conteúdo está na resposta. outra coisa deve estar a olhar para "produz" para determinar o tipo de conteúdo para set
anton1980
6

Você pode usar produz para indicar o tipo de resposta que está enviando do controlador. Essa palavra-chave "produz" será mais útil na solicitação do ajax e foi muito útil no meu projeto

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

public @ResponseBody String getMobileData() {

}
Jayaraman Balasubramanian
fonte
4

Obrigado digz6666, sua solução funciona para mim com pequenas alterações porque estou usando json:

responseHeaders.add ("Tipo de Conteúdo", "application / json; charset = utf-8");

A resposta dada por axtavt (que você recomendou) não funcionará para mim. Mesmo se eu adicionei o tipo de mídia correto:

if (conv instanceof StringHttpMessageConverter) {                   
                    ((StringHttpMessageConverter) conv) .setSupportedMediaTypes (
                        Arrays.asList (
                                novo MediaType ("texto", "html", Charset.forName ("UTF-8")),
                                novo MediaType ("aplicativo", "json", Charset.forName ("UTF-8"))));
                }
redochka
fonte
4

Defino o tipo de conteúdo no MarshallingView no bean ContentNegotiatingViewResolver . Funciona com facilidade, limpeza e suavidade:

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

Estou usando o CharacterEncodingFilter, configurado em web.xml. Talvez isso ajude.

    <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
fonte
1
Isso apenas filtra o caractere na solicitação, não na resposta - eu já estou usando este aqui
Hurda
@Hurda: Com forceEncoding=trueele filtra a resposta também, mas não ajudaria neste caso.
axtavt 01/09/10
Melhor e mais rápida resposta até agora. Eu também já estava declarando e usando esse filtro, mas com forceEncoding=false. Acabei de defini-lo falsee "charset = UTF-8" foi adicionado com sucesso ao Content-Typecabeçalho.
Saad Benbouzid 25/10
2

se nenhuma das opções acima funcionou para você tentar fazer solicitações de ajax no "POST" e não no "GET", funcionou muito bem para mim ... nenhuma das opções acima funcionou. Eu também tenho o characterEncodingFilter.

Marius
fonte
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");
    }
}

Depois de ter tentado muitas soluções alternativas para esse problema. Pensei nisso e funciona bem.

Szilard Jakab
fonte
2

A maneira simples de resolver esse problema no Spring 3.1.1 é: adicione os seguintes códigos de configuração em 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>

Não precisa substituir ou implementar nada.

AdaroMu
fonte
2

se você decidir corrigir esse problema através da seguinte configuração:

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

você deve confirmar que deve haver apenas uma tag controlada por anotação mvc: em todo o seu arquivo * .xml. caso contrário, a configuração pode não ser eficaz.

Lich
fonte
1

De acordo com o link "Se uma codificação de caracteres não for especificada, a especificação Servlet exige que uma codificação ISO-8859-1 seja usada". Se você estiver usando o Spring 3.1 ou posterior, use a seguinte configuração para definir charset = UTF-8 para corpo da resposta
@RequestMapping (value = "sua URL de mapeamento", produz = "text / plain; charset = UTF-8")

Ramesh Papaganti
fonte
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;
        }
    }
}

Configuração de amostra:

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