Kto ustawia typ zawartości odpowiedzi w Spring MVC (@ResponseBody)


126

Mam w mojej aplikacji internetowej Spring MVC Java opartej na adnotacjach uruchomionej na serwerze internetowym jetty (obecnie we wtyczce maven jetty).

Próbuję zrobić wsparcie AJAX z jedną metodą kontrolera zwracającą tylko tekst pomocy typu String. Zasoby są w kodowaniu UTF-8, podobnie jak łańcuch, ale moja odpowiedź z serwera jest dołączona

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

nawet gdy moja przeglądarka wysyła

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

Jakoś używam domyślnej konfiguracji sprężyny

Znalazłem wskazówkę, aby dodać tę fasolę do konfiguracji, ale myślę, że po prostu nie jest używana, ponieważ mówi, że nie obsługuje kodowania i zamiast tego używana jest domyślna.

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

Mój kod kontrolera to (zauważ, że ta zmiana typu odpowiedzi nie działa dla mnie):

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

Odpowiedzi:


59

Prosta deklaracja StringHttpMessageConverterfasoli nie wystarczy, należy ją wstrzyknąć do 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>

Jednak używając tej metody musisz przedefiniować wszystkie HttpMessageConverters, a także nie działa<mvc:annotation-driven /> .

Więc chyba najwygodniejszą, ale brzydką metodą jest przechwycenie instancji AnnotationMethodHandlerAdapterz 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
Wygląda na to, że to brudny hack. Nie lubię tego, ale używam. Programiści wiosennych frameworków powinni pracować nad tym przypadkiem!
digz6666,

Gdzie prowadzi wiersz <bean class = "EncodingPostProcessor" />?
zod

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

Dzięki. Wydaje się, że jest ignorowany. Używamy mvc (chyba) i mamy klasę z atrybutem @Controller, który wydaje się być punktem wejścia. Klasa nie jest nigdzie wspomniana (ma interfejs o podobnej nazwie), ale jest tworzona i wywoływana poprawnie. Ścieżki są mapowane za pomocą atrybutu @RequestMapping. Nie jesteśmy w stanie kontrolować typu treści odpowiedzi (potrzebujemy xml). Jak pewnie wiesz, nie mam pojęcia, co robię, a programista, który to stworzył, opuścił moją firmę. Dzięki.
zod

3
Jak mówi @ digz6666, to jest brudny hack. Wiosna powinna zobaczyć, jak robi to JAX-RS.
Adam Gent,

166

Znalazłem rozwiązanie dla Spring 3.1. z użyciem adnotacji @ResponseBody. Oto przykład kontrolera używającego wyjścia Json:

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

}

7
+1. To również rozwiązało problem, ale dopiero po przełączeniu się na używanie <mvc:annotation-driven/>w applicationContext. (Zamiast tego <bean class=" [...] DefaultAnnotationHandlerMapping"/>, co i tak jest przestarzałe w wersji Spring 3.2 ...)
Jonik

czy to produkuje aplikację / xml, jeśli jest to opisane w ten sposób?
Hurda,

2
@Hurda: Oczywiście możesz określić dowolny typ zawartości, który chcesz, zmieniając wartość producesatrybutu.
Jonik

1
Istnieje również MediaType.APPLICATION_JSON_VALUE dla „application / json”.
dev

2
W przypadku UTF-8 zobacz MediaType.APPLICATION_JSON_UTF8_VALUE.
calvinf

51

Zauważ, że w Spring MVC 3.1 możesz użyć przestrzeni nazw MVC do skonfigurowania konwerterów komunikatów:

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

Lub konfiguracja oparta na kodzie:

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

Rodzaj działa, z wyjątkiem tego, że 1) zanieczyszcza odpowiedź Accept-Charsetnagłówkiem, który prawdopodobnie zawiera wszystkie znane kodowania znaków, oraz 2) gdy żądanie ma Acceptnagłówek, supportedMediaTypeswłaściwość konwertera nie jest używana , więc na przykład gdy wykonuję żądanie wpisując bezpośrednio adres URL w przeglądarce odpowiedź ma Content-Type: text/htmlzamiast tego nagłówek.
Giulio Piancastelli

3
Możesz to uprościć, ponieważ „tekst / zwykły” jest i tak domyślny: <bean class="org.springframework.http.converter.StringHttpMessageConverter"><constructor-arg value="UTF-8" /></bean>
Igor Mukhin

Tę odpowiedź należy przyjąć jako właściwą. Również sposób definiowania beana StringHttpMessageConverter przez @IgorMukhin działa. Ta odpowiedź służy do ustawiania typów treści odpowiedzi dla wszystkich serwletów. Jeśli potrzebujesz tylko ustawić typ zawartości odpowiedzi dla określonej metody kontrolera, użyj zamiast tego odpowiedzi Warrior (użyj argumentu produkuje w @RequestMapping)
PickBoy

3
@GiulioPiancastelli Twoje pierwsze pytanie można rozwiązać, dodając <property name = "writeAcceptCharset" value = "false" /> do fasoli
PickBoy

44

Na wszelki wypadek możesz również ustawić kodowanie w następujący sposób:

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

Myślę, że użycie StringHttpMessageConverter jest lepsze niż to.


Jest to również rozwiązanie w przypadku wystąpienia błędu the manifest may not be valid or the file could not be opened.w IE 11. Dzięki digz!
Arun Christopher

21

możesz dodaćsters = "text / plain; charset = UTF-8" do 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);
}

zobacz ten blog, aby uzyskać więcej informacji


2
Ten kod nie mógł się skompilować; zwracasz coś z metody Void.
Andrew Swan

2
przepraszam, zły błąd, został już naprawiony
Charlie Wu

3
To jest błędna odpowiedź. Zgodnie z dokumentacją wiosny: możliwe do wyprodukowania typy mediów zamapowanego żądania, zawężające mapowanie podstawowe. Format to sekwencja typów multimediów („tekst / zwykły”, „aplikacja / *), z żądaniem mapowanym tylko wtedy, gdy akceptacja pasuje do jednego z tych typów multimediów. Wyrażenia można zanegować za pomocą operatora„! ”, Jak w „! text / plain”, które odpowiada wszystkim żądaniom z akceptacją inną niż „text / plain”.
Oleksandr_DJ

@CharlieWu Wystąpił problem z linkiem
Matt

10

Niedawno walczyłem z tym problemem i znalazłem znacznie lepszą odpowiedź dostępną na wiosnę 3.1:

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

Tak więc, tak proste, jak JAX-RS, tak jak wszystkie komentarze wskazywały, że może / powinno być.


Warto przenieść się na Spring 3.1 za!
young.fu.panda

5
@dbyoung To nie wydaje się właściwe, javadoc dla producesmówi: "... żądanie jest mapowane tylko wtedy, gdy typ zawartości pasuje do jednego z tych typów mediów." co oznacza AFAIK, że producesma znaczenie dla tego, czy metoda pasuje do żądania, a nie jak typ zawartości powinna mieć odpowiedź.
Ittai,

@Ittai poprawne! „Produkuje” określa, czy metoda pasuje do żądania, ale NIE określa, jaki typ zawartości jest w odpowiedzi. coś innego musi patrzeć na „produkuje” podczas określania, jaki typ zawartości ustawić
anton1980

6

Możesz użyć produkcji, aby wskazać typ odpowiedzi, którą wysyłasz z kontrolera. To słowo kluczowe "produkuje" będzie najbardziej przydatne w żądaniu Ajax i było bardzo pomocne w moim projekcie

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

public @ResponseBody String getMobileData() {

}

4

Dzięki digz6666, Twoje rozwiązanie działa dla mnie z niewielkimi zmianami, ponieważ używam json:

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

Odpowiedź udzielona przez axtavt (którą poleciłeś) nie zadziała dla mnie. Nawet jeśli dodałem właściwy typ nośnika:

if (conv instanceof StringHttpMessageConverter) {                   
                    ((StringHttpMessageConverter) konw.) .SetSupportedMediaTypes (
                        Arrays.asList (
                                new MediaType ("text", "html", Charset.forName ("UTF-8")),
                                new MediaType ("application", "json", Charset.forName ("UTF-8"))));
                }

4

Ustawiam typ zawartości w MarshallingView w fasoli ContentNegotiatingViewResolver . Działa łatwo, czysto i płynnie:

<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

Używam CharacterEncodingFilter, skonfigurowanego w web.xml. Może to pomoże.

    <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
To tylko filtruje znak w żądaniu, a nie w odpowiedzi - już używam tego
Hurda

@Hurda: Dzięki forceEncoding=truetemu również filtruje odpowiedź, ale w tym przypadku nie pomoże.
axtavt

Jak dotąd najlepsza i szybsza odpowiedź. Ja też już deklarowałem i używam tego filtra, ale z forceEncoding=false. Po prostu ustawiłem go na falsei „charset = UTF-8” został pomyślnie dodany do Content-Typenagłówka.
Saad Benbouzid

2

jeśli żadne z powyższych nie zadziałało, spróbuj wykonać żądania AJAX na "POST", a nie "GET", to działało dla mnie dobrze ... żadne z powyższych nie zadziałało. Mam również 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");
    }
}

Po wypróbowaniu wielu sposobów obejścia tego problemu ... Przemyślałem to i działa dobrze.


2

Prosty sposób rozwiązania tego problemu w Spring 3.1.1 jest następujący: dodaj następujące kody konfiguracji w 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>

Nie musisz niczego zastępować ani wdrażać.


2

jeśli zdecydujesz się rozwiązać ten problem za pomocą następującej konfiguracji:

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

powinieneś potwierdzić, że w całym pliku * .xml powinien znajdować się tylko jeden tag oparty na adnotacjach mvc:. w przeciwnym razie konfiguracja może nie być skuteczna.


1

Zgodnie z odsyłaczem „Jeśli kodowanie znaków nie jest określone, specyfikacja serwletu wymaga użycia kodowania ISO-8859-1”. Jeśli używasz wiosny 3.1 lub nowszej, użyj poniższej konfiguracji, aby ustawić charset = UTF-8 na treść odpowiedzi
@RequestMapping (value = "twój adres URL mapowania", produkuje = "tekst / zwykły; 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;
        }
    }
}

Przykładowa konfiguracja:

    <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>
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.