Chi imposta il tipo di contenuto della risposta in Spring MVC (@ResponseBody)


126

Sto avendo nella mia applicazione Web Spring MVC Java guidata da annotazioni gestita sul server web jetty (attualmente nel plugin molo jven).

Sto provando a fare un po 'di supporto AJAX con un metodo controller che restituisce solo il testo di aiuto di String. Le risorse sono nella codifica UTF-8, così come la stringa, ma arriva la mia risposta dal server

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

anche quando il mio browser invia

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

Sto usando in qualche modo la configurazione predefinita di Spring

Ho trovato un suggerimento per aggiungere questo bean alla configurazione, ma penso che non sia usato, perché dice che non supporta la codifica e ne viene utilizzato uno predefinito.

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

Il mio codice controller è (nota che questa modifica del tipo di risposta non funziona per me):

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

Risposte:


59

La semplice dichiarazione del StringHttpMessageConverterbean non è sufficiente, è necessario iniettarlo in 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>

Tuttavia, usando questo metodo devi ridefinire tutti HttpMessageConverteri messaggi di posta elettronica , e inoltre non funziona<mvc:annotation-driven /> .

Quindi, forse il metodo più conveniente ma brutto è intercettare l'istanza di AnnotationMethodHandlerAdaptercon 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 " />

10
Sembra un trucco sporco. Non mi piace ma da usare. Gli sviluppatori del framework Spring dovrebbero lavorare su questo caso!
digz6666

Dove va la riga <bean class = "EncodingPostProcessor" />?
zod

1
@zod: In DispatcherServlet's config ( ...-servlet.xml)
axtavt

Grazie. Sembra essere ignorato. Stiamo usando mvc (credo) e abbiamo una classe con un attributo @Controller, che sembra essere il punto di ingresso. La classe non è menzionata altrove (ha un'interfaccia con un nome simile) ma è istanziata e chiamata correttamente. I percorsi sono associati con un attributo @RequestMapping. Non siamo in grado di controllare il tipo di contenuto della risposta (abbiamo bisogno di xml). Come probabilmente puoi dire, non ho idea di cosa sto facendo e lo sviluppatore che ha creato questo ha lasciato la mia azienda. Grazie.
zod

3
Come dice @ digz6666 questo è un trucco sporco. La primavera dovrebbe vedere come fa JAX-RS.
Adam Gent,

166

Ho trovato una soluzione per la primavera 3.1. con l'utilizzo dell'annotazione @ResponseBody. Ecco un esempio di controller che utilizza l'output Json:

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

}

7
+1. Questo ha risolto anche per me, ma solo dopo che sono passato all'utilizzo <mvc:annotation-driven/>in applicationContext. (Invece di <bean class=" [...] DefaultAnnotationHandlerMapping"/>, che è deprecato in primavera 3.2 comunque ...)
Jonik

questo produce application / xml se annotato in questo modo?
Hurda,

2
@Hurda: Ovviamente puoi specificare qualsiasi tipo di contenuto che desideri modificando il valore producesdell'attributo.
Jonik,

1
Esiste un MediaType.APPLICATION_JSON_VALUE, anche per "application / json".
dev

2
Per UTF-8, vedere MediaType.APPLICATION_JSON_UTF8_VALUE.
Calvinf,

51

Si noti che in Spring MVC 3.1 è possibile utilizzare lo spazio dei nomi MVC per configurare i convertitori di messaggi:

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

O configurazione basata su codice:

@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 ...
  }
}

In un certo senso funziona, tranne che 1) inquina la risposta con Accept-Charsetun'intestazione che probabilmente elenca ogni codifica dei caratteri noti e 2) quando la richiesta ha Acceptun'intestazione la supportedMediaTypesproprietà del convertitore non viene utilizzata , quindi ad esempio quando faccio la digitazione della richiesta direttamente l'URL in un browser la risposta ha Content-Type: text/htmlinvece un'intestazione.
Giulio Piancastelli,

3
Puoi semplificare in quanto "text / plain" è comunque predefinito: <bean class="org.springframework.http.converter.StringHttpMessageConverter"><constructor-arg value="UTF-8" /></bean>
Igor Mukhin,

Questa risposta dovrebbe essere accettata come la risposta giusta. Inoltre, il modo di @IgorMukhin di definire il bean StringHttpMessageConverter funziona. Questa risposta viene utilizzata per impostare i tipi di contenuto di risposta per tutti i servlet. Se devi solo impostare il tipo di contenuto di risposta per un particolare metodo di controller, usa invece la risposta di Warrior (usa produce argomento in @RequestMapping)
PickBoy

3
@GiulioPiancastelli la tua prima domanda può essere risolta aggiungendo <nome proprietà = "writeAcceptCharset" valore = "false" /> al bean
PickBoy

44

Nel caso in cui sia possibile anche impostare la codifica nel modo seguente:

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

Penso che usare StringHttpMessageConverter sia meglio di così.


Questa è anche la soluzione se si ottiene l'errore the manifest may not be valid or the file could not be opened.in IE 11. Grazie digz!
Arun Christopher,

21

puoi aggiungere produce = "text / plain; charset = UTF-8" a 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);
}

vedi questo blog per maggiori dettagli


2
Quel codice non si sarebbe compilato; stai restituendo qualcosa da un metodo vuoto.
Andrew Swan,

2
scusa cattivo bug, ora è stato risolto
Charlie Wu,

3
È una risposta errata Secondo i documenti di primavera: i tipi di media producibili della richiesta mappata, restringendo la mappatura primaria. Il formato è una sequenza di tipi di media ("text / plain", "application / *), con una richiesta mappata solo se Accept corrisponde a uno di questi tipi di media. Le espressioni possono essere annullate usando l'operatore"! ", Come in "! text / plain", che corrisponde a tutte le richieste con un Accept diverso da "text / plain".
Oleksandr_DJ

@CharlieWu Si è verificato un problema con il collegamento
Matt,

10

Stavo combattendo questo problema di recente e ho trovato una risposta molto migliore disponibile nella primavera 3.1:

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

Quindi, facile come JAX-RS, proprio come tutti i commenti hanno indicato che potrebbe / dovrebbe essere.


Vale la pena porting su Spring 3.1 per!
young.fu.panda,

5
@dbyoung Questo non sembra giusto, il javadoc per il producesdice: "... richiesta mappata solo se il Content-Type corrisponde a uno di questi tipi di media." il che significa che AFAIK producesè rilevante per stabilire se il metodo corrisponde a una richiesta e non su quale tipo di contenuto dovrebbe avere la risposta.
Ittai,

@Ittai corretto! "produce" determina se il metodo corrisponde alla richiesta, ma NON quale tipo di contenuto è nella risposta. qualcos'altro deve guardare "produce" quando si determina quale tipo di contenuto impostare
anton1980

6

È possibile utilizzare produce per indicare il tipo di risposta che si sta inviando dal controller. Questa parola chiave "produce" sarà molto utile nella richiesta Ajax ed è stata molto utile nel mio progetto

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

public @ResponseBody String getMobileData() {

}

4

Grazie digz6666, la tua soluzione funziona per me con lievi modifiche perché sto usando JSON:

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

La risposta data da axtavt (che mi hai consigliato) non funzionerà per me. Anche se ho aggiunto il tipo di supporto corretto:

if (conv istanza di StringHttpMessageConverter) {                   
                    ((StringHttpMessageConverter) conv) .setSupportedMediaTypes (
                        Arrays.asList (
                                nuovo MediaType ("text", "html", Charset.forName ("UTF-8")),
                                nuovo MediaType ("application", "json", Charset.forName ("UTF-8"))));
                }

4

Ho impostato il tipo di contenuto in MarshallingView nel bean ContentNegotiatedViewResolver . Funziona facilmente, pulito e senza intoppi:

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

3

Sto usando CharacterEncodingFilter, configurato in web.xml. Forse questo aiuta.

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

1
Questo filtra solo il carattere su richiesta, non in risposta - lo sto già usando
Hurda

@Hurda: con forceEncoding=trueesso filtra anche la risposta, ma in questo caso non sarebbe d'aiuto.
axtavt,

La risposta migliore e più veloce finora. Inoltre stavo già dichiarando e usando questo filtro, ma con forceEncoding=false. L'ho appena impostato falsee "charset = UTF-8" è stato aggiunto con successo all'intestazione Content-Type.
Saad Benbouzid,

2

se nessuna delle precedenti ha funzionato per te, prova a fare richieste Ajax su "POST" e non "GET", ha funzionato bene per me ... nessuna delle precedenti. Ho anche il characterEncodingFilter.


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

Dopo aver provato molte soluzioni alternative per questo problema ... Ci ho pensato e funziona benissimo.


2

Il modo semplice per risolvere questo problema nella primavera 3.1.1 è quello: aggiungere i seguenti codici di configurazione in 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>

Non è necessario sovrascrivere o implementare nulla.


2

se decidi di risolvere questo problema attraverso la seguente configurazione:

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

dovresti confermare che dovrebbe esserci un solo tag mvc: basato sulle annotazioni in tutto il tuo file * .xml. in caso contrario, la configurazione potrebbe non essere effettiva.


1

Secondo il link "Se non viene specificata una codifica dei caratteri, la specifica Servlet richiede che venga utilizzata una codifica ISO-8859-1". Se si utilizza la primavera 3.1 o successive, utilizzare la configurazione incerta per impostare charset = UTF-8 su response body
@RequestMapping (value = "your mapping url", produce = "text / plain; charset = UTF-8")


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

Configurazione di esempio:

    <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>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.