Wer legt den Inhaltstyp der Antwort in Spring MVC fest (@ResponseBody)

126

Ich habe in meiner Annotation-gesteuerten Spring MVC Java-Webanwendung auf dem Jetty-Webserver ausgeführt (derzeit im Maven Jetty-Plugin).

Ich versuche, AJAX-Unterstützung mit einer Controller-Methode durchzuführen, die nur String-Hilfetext zurückgibt. Die Ressourcen sind in UTF-8-Codierung, ebenso wie die Zeichenfolge, aber meine Antwort vom Server kommt mit

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

auch wenn mein browser sendet

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

Ich verwende irgendwie die Standardkonfiguration der Feder

Ich habe einen Hinweis gefunden, um diese Bean zur Konfiguration hinzuzufügen, aber ich denke, sie wird einfach nicht verwendet, da sie besagt, dass sie die Codierung nicht unterstützt und stattdessen eine Standardcodierung verwendet wird.

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

Mein Controller-Code lautet (beachten Sie, dass diese Änderung des Antworttyps bei mir nicht funktioniert):

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

Antworten:

59

Eine einfache Deklaration der StringHttpMessageConverterBohne reicht nicht aus, Sie müssen sie injizieren 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>

Mit dieser Methode müssen Sie jedoch alle HttpMessageConverters neu definieren , und es funktioniert auch nicht mit <mvc:annotation-driven />.

Die vielleicht bequemste, aber hässlichste Methode ist es, die Instanziierung des AnnotationMethodHandlerAdaptermit abzufangen 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
quelle
10
Es scheint wie ein schmutziger Hack. Ich mag es nicht, aber zu benutzen. Entwickler von Spring Frameworks sollten an diesem Fall arbeiten!
digz6666
Wohin geht die Zeile <bean class = "EncodingPostProcessor" />?
Zod
1
@zod: In DispatcherServlet's config ( ...-servlet.xml)
axtavt
Vielen Dank. Es scheint ignoriert zu werden. Wir verwenden mvc (glaube ich) und wir haben eine Klasse mit einem @ Controller-Attribut, das der Einstiegspunkt zu sein scheint. Die Klasse wird nirgendwo anders erwähnt (sie hat eine Schnittstelle mit einem ähnlichen Namen), sie wird jedoch instanziiert und korrekt aufgerufen. Pfade werden mit einem @ RequestMapping-Attribut zugeordnet. Wir können den Inhaltstyp der Antwort nicht steuern (wir benötigen XML). Wie Sie wahrscheinlich sehen können, habe ich keine Ahnung, was ich tue, und der Entwickler, der dies erstellt hat, hat meine Firma verlassen. Vielen Dank.
Zod
3
Wie @ digz6666 sagt, ist dies ein schmutziger Hack. Der Frühling sollte sehen, wie JAX-RS das macht.
Adam Gent
166

Ich habe eine Lösung für Spring 3.1 gefunden. mit der Verwendung der @ ResponseBody-Annotation. Hier ist ein Beispiel für einen Controller mit Json-Ausgabe:

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

}
Krieger
quelle
7
+1. Dies löste es auch für mich, aber erst nachdem ich zur Verwendung <mvc:annotation-driven/>in applicationContext gewechselt war . (Statt <bean class=" [...] DefaultAnnotationHandlerMapping"/>, was im Frühjahr 3.2 sowieso veraltet ist ...)
Jonik
Kann dies zu einer Anwendung / XML führen, wenn dies auf diese Weise kommentiert wird?
Hurda
2
@ Hurda: Natürlich können Sie jeden gewünschten Inhaltstyp angeben, indem Sie den Wert des producesAttributs ändern .
Jonik
1
Es gibt auch einen MediaType.APPLICATION_JSON_VALUE für "application / json".
Dev
2
Informationen zu UTF-8 finden Sie unter MediaType.APPLICATION_JSON_UTF8_VALUE.
Calvinf
51

Beachten Sie, dass Sie in Spring MVC 3.1 den MVC-Namespace verwenden können, um Nachrichtenkonverter zu konfigurieren:

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

Oder codebasierte Konfiguration:

@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
quelle
Art von Arbeiten, außer dass 1) die Antwort mit einem Accept-CharsetHeader verschmutzt wird, der wahrscheinlich jede bekannte Zeichenkodierung auflistet, und 2) wenn die Anfrage einen AcceptHeader hat, die supportedMediaTypesEigenschaft des Konverters nicht verwendet wird , also zum Beispiel, wenn ich die Anfrage tippe direkt die URL in einem Browser die Antwort hat Content-Type: text/htmlstattdessen einen Header.
Giulio Piancastelli
3
Sie können vereinfachen, da "Text / Plain" sowieso Standard ist: <bean class="org.springframework.http.converter.StringHttpMessageConverter"><constructor-arg value="UTF-8" /></bean>
Igor Mukhin
Diese Antwort sollte als die richtige Antwort akzeptiert werden. Außerdem funktioniert @IgorMukhins Methode zum Definieren der StringHttpMessageConverter-Bean. Diese Antwort wird verwendet, um Antwortinhaltstypen für alle Servlets festzulegen. Wenn Sie nur den Antwortinhaltstyp für eine bestimmte Controller-Methode festlegen müssen, verwenden Sie stattdessen die Antwort von Warrior (Verwendung erzeugt Argumente in @RequestMapping)
PickBoy
3
@ GiulioPiancastelli Ihre erste Frage kann gelöst werden, indem Sie <property name = "writeAcceptCharset" value = "false" /> zur Bean
hinzufügen
44

Nur für den Fall, dass Sie die Codierung auch folgendermaßen einstellen können:

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

Ich denke, StringHttpMessageConverter ist besser als dies.

digz6666
quelle
Dies ist auch die Lösung, wenn Sie den Fehler the manifest may not be valid or the file could not be opened.in IE 11 erhalten. Danke digz!
Arun Christopher
21

Sie können "RequestMapping" = = "text / plain; charset = UTF-8" hinzufügen

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

Weitere Informationen finden Sie in diesem Blog

Charlie Wu
quelle
2
Dieser Code würde nicht kompiliert werden. Sie geben etwas von einer ungültigen Methode zurück.
Andrew Swan
2
Entschuldigung, schlechter Fehler, es ist jetzt behoben
Charlie Wu
3
Es ist eine falsche Antwort. Gemäß Frühlingsdokumenten: Die produzierbaren Medientypen der zugeordneten Anforderung, wodurch die primäre Zuordnung eingegrenzt wird. Das Format ist eine Folge von Medientypen ("text / plain", "application / *)", wobei eine Anforderung nur zugeordnet wird, wenn "Akzeptieren" mit einem dieser Medientypen übereinstimmt. Ausdrücke können mit dem Operator "!" Wie in negiert werden "! text / plain", das alle Anfragen mit einem anderen Akzept als "text / plain"
Oleksandr_DJ
@CharlieWu Es gibt ein Problem mit dem Link
Matt
10

Ich habe kürzlich gegen dieses Problem gekämpft und in Frühjahr 3.1 eine viel bessere Antwort gefunden:

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

So einfach wie JAX-RS, genau wie in allen Kommentaren angegeben, dass es sein könnte / sollte.

dbyoung
quelle
Es lohnt sich, auf Spring 3.1 zu portieren!
young.fu.panda
5
@dbyoung Das scheint nicht richtig zu sein, der Javadoc für die producessagt: "... Anfrage nur zugeordnet, wenn der Inhaltstyp einem dieser Medientypen entspricht." Dies bedeutet AFAIK, dass die producesFrage relevant ist, ob die Methode mit einer Anforderung übereinstimmt und nicht, welchen Inhaltstyp die Antwort haben sollte.
Ittai
@ Ittai richtig! "produziert" bestimmt, ob die Methode mit der Anforderung übereinstimmt, NICHT jedoch, welcher Inhaltstyp in der Antwort enthalten ist. etwas anderes muss auf "produziert" schauen, wenn bestimmt wird, welcher Inhaltstyp eingestellt werden soll
anton1980
6

Mithilfe von Erzeugnissen können Sie den Typ der Antwort angeben, die Sie vom Controller senden. Dieses Schlüsselwort "produziert" ist bei Ajax-Anfragen am nützlichsten und war in meinem Projekt sehr hilfreich

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

public @ResponseBody String getMobileData() {

}
Balasubramanian Jayaraman
quelle
4

Dank digz6666 funktioniert Ihre Lösung für mich mit geringfügigen Änderungen, da ich json verwende:

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

Die Antwort von axtavt (die Sie empfohlen haben) funktioniert bei mir nicht. Auch wenn ich den richtigen Medientyp hinzugefügt habe:

if (Conv-Instanz von StringHttpMessageConverter) {                   
                    ((StringHttpMessageConverter) conv) .setSupportedMediaTypes (
                        Arrays.asList (
                                neuer Medientyp ("Text", "HTML", Charset.forName ("UTF-8")),
                                neuer Medientyp ("application", "json", Charset.forName ("UTF-8")));
                }}
Redochka
quelle
4

Ich habe den Inhaltstyp in der MarshallingView in der ContentNegotiatingViewResolver- Bean festgelegt. Es funktioniert einfach, sauber und reibungslos:

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

Ich verwende den CharacterEncodingFilter, der in web.xml konfiguriert ist. Vielleicht hilft das.

    <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 Schnee
quelle
1
Dies filtert nur Zeichen auf Anfrage, nicht als Antwort - ich benutze dieses bereits
Hurda
@ Hurda: Damit forceEncoding=truewird auch die Antwort gefiltert, aber in diesem Fall würde es nicht helfen.
Axtavt
Beste und schnellere Antwort bisher. Ich habe diesen Filter auch schon deklariert und benutzt, aber mit forceEncoding=false. Ich habe es gerade eingestellt falseund "charset = UTF-8" wurde erfolgreich zum Content-TypeHeader hinzugefügt .
Saad Benbouzid
2

Wenn keiner der oben genannten Punkte für Sie funktioniert hat und Sie versuchen, Ajax-Anfragen für "POST" und nicht für "GET" zu stellen, hat das für mich gut funktioniert ... keiner der oben genannten Punkte hat funktioniert. Ich habe auch den Charakter EncodingFilter.

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

Nachdem ich viele Problemumgehungen für dieses Problem ausprobiert habe, habe ich mir das ausgedacht und es funktioniert einwandfrei.

Szilard Jakab
quelle
2

Die einfache Möglichkeit, dieses Problem in Spring 3.1.1 zu lösen, besteht darin, folgende Konfigurationscodes hinzuzufügen 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>

Sie müssen nichts überschreiben oder implementieren.

AdaroMu
quelle
2

Wenn Sie dieses Problem durch die folgende Konfiguration beheben möchten:

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

Sie sollten bestätigen, dass in Ihrer gesamten * .xml-Datei nur ein mvc: annotation-gesteuertes Tag vorhanden sein sollte. Andernfalls ist die Konfiguration möglicherweise nicht wirksam.

Lich
quelle
1

Gemäß dem Link "Wenn keine Zeichenkodierung angegeben ist, erfordert die Servlet-Spezifikation die Verwendung einer Kodierung nach ISO-8859-1". Wenn Sie Spring 3.1 oder höher verwenden, verwenden Sie die folgende Konfiguration, um charset = UTF-8 auf zu setzen Antworttext
@RequestMapping (value = "Ihre Zuordnungs-URL", erzeugt = "text / plain; charset = UTF-8")

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

Beispielkonfiguration:

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