Yang menetapkan tipe konten respons di Spring MVC (@ResponseBody)

126

Saya mengalami di aplikasi web Anotasi didorong Spring MVC Java saya dijalankan di server web jetty (saat ini dalam plugin maven jetty).

Saya mencoba melakukan beberapa dukungan AJAX dengan satu metode pengontrol mengembalikan hanya teks bantuan String. Sumber daya dalam pengkodean UTF-8 dan demikian juga string, tetapi tanggapan saya dari server datang bersama

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

bahkan ketika browser saya mengirim

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

Saya menggunakan konfigurasi default entah bagaimana pegas

Saya telah menemukan petunjuk untuk menambahkan kacang ini ke konfigurasi, tetapi saya pikir itu tidak digunakan, karena dikatakan tidak mendukung pengkodean dan yang default digunakan sebagai gantinya.

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

Kode pengontrol saya adalah (perhatikan bahwa perubahan jenis respons ini tidak berfungsi untuk saya):

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

Jawaban:

59

Pernyataan sederhana StringHttpMessageConverterkacang tidak cukup, Anda harus menyuntikkannya ke 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>

Namun, dengan menggunakan metode ini Anda harus mendefinisikan ulang semua HttpMessageConverters, dan juga tidak berhasil <mvc:annotation-driven />.

Jadi, mungkin metode yang paling mudah tetapi jelek adalah untuk mencegat instantiation AnnotationMethodHandlerAdapterdengan 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
sumber
10
Sepertinya hack kotor. Saya tidak suka tetapi untuk digunakan. Pengembang kerangka kerja semi harus bekerja dalam kasus ini!
digz6666
Di mana baris <bean class = "EncodingPostProcessor" /> pergi?
zod
1
@ zod: In DispatcherServlet's config ( ...-servlet.xml)
axtavt
Terima kasih. Tampaknya diabaikan. Kami menggunakan mvc (saya pikir) dan kami memiliki kelas dengan atribut @Controller, yang tampaknya menjadi titik masuk. Kelas tidak disebutkan di tempat lain (ia memiliki antarmuka dengan nama yang serupa) namun ia dipakai dan dipanggil dengan benar. Paths dipetakan dengan atribut @RequestMapping. Kami tidak dapat mengontrol tipe konten dari respons (kami perlu xml). Seperti yang mungkin Anda tahu, saya tidak tahu apa yang saya lakukan, dan pengembang yang menciptakan ini telah meninggalkan perusahaan saya. Terima kasih.
zod
3
Seperti @ digz6666 katakan ini adalah hack kotor. Spring seharusnya melihat bagaimana JAX-RS melakukannya.
Adam Gent
166

Saya menemukan solusi untuk Spring 3.1. dengan menggunakan anotasi @ResponseBody. Berikut adalah contoh pengontrol menggunakan output Json:

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

}
pejuang
sumber
7
+1. Ini menyelesaikannya untuk saya juga, tetapi hanya setelah saya beralih menggunakan <mvc:annotation-driven/>di applicationContext. (Alih-alih <bean class=" [...] DefaultAnnotationHandlerMapping"/>, yang sudah usang di Spring 3.2 toh ...)
Jonik
ca ini menghasilkan aplikasi / xml jika dijelaskan dengan cara ini?
Hurda
2
@Hurda: Jelas Anda dapat menentukan jenis konten apa pun yang Anda inginkan dengan mengubah nilai producesatribut.
Jonik
1
Ada MediaType.APPLICATION_JSON_VALUE, untuk "application / json" juga.
dev
2
Untuk UTF-8, lihat MediaType.APPLICATION_JSON_UTF8_VALUE.
calvinf
51

Perhatikan bahwa di Spring MVC 3.1 Anda dapat menggunakan namespace MVC untuk mengonfigurasi konverter pesan:

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

Atau konfigurasi berbasis kode:

@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
sumber
Semacam karya, kecuali bahwa 1) itu mencemari respons dengan Accept-Charsetheader yang mungkin mencantumkan setiap pengkodean karakter yang diketahui, dan 2) ketika permintaan memiliki Acceptheader supportedMediaTypesproperti dari konverter tidak digunakan , jadi misalnya ketika saya membuat permintaan mengetik langsung URL di browser responsnya memiliki Content-Type: text/htmltajuk.
Giulio Piancastelli
3
Anda dapat menyederhanakan karena "teks / polos" adalah default: <bean class="org.springframework.http.converter.StringHttpMessageConverter"><constructor-arg value="UTF-8" /></bean>
Igor Mukhin
Jawaban ini harus diterima sebagai jawaban yang benar. Juga, cara @IgorMukhin untuk mendefinisikan karya kacang StringHttpMessageConverter. Jawaban ini digunakan untuk mengatur tipe konten respons untuk semua servlet. Jika Anda hanya perlu mengatur tipe konten tanggapan untuk metode pengontrol tertentu, gunakan jawaban Warrior sebagai gantinya (gunakan menghasilkan argumen di @RequestMapping)
PickBoy
3
@GiulioPiancastelli pertanyaan pertama Anda dapat diselesaikan dengan menambahkan <properti name = "writeAcceptCharset" value = "false" /> ke kacang
PickBoy
44

Untuk berjaga-jaga Anda juga dapat mengatur pengkodean dengan cara berikut:

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

Saya pikir menggunakan StringHttpMessageConverter lebih baik dari ini.

digz6666
sumber
Ini juga solusinya jika Anda mendapatkan kesalahan the manifest may not be valid or the file could not be opened.di IE 11. Terima kasih digz!
Arun Christopher
21

Anda dapat menambahkan menghasilkan = "text / plain; charset = UTF-8" ke 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);
}

lihat blog ini untuk lebih detail

Charlie Wu
sumber
2
Kode itu tidak dapat dikompilasi; Anda mengembalikan sesuatu dari metode batal.
Andrew Swan
2
maaf bug buruk, sudah diperbaiki sekarang
Charlie Wu
3
Itu jawaban yang salah. Sesuai dokumen pegas: Jenis media yang dapat diproduksi dari permintaan yang dipetakan, mempersempit pemetaan primer. Formatnya adalah urutan jenis media ("teks / polos", "aplikasi / *), dengan permintaan hanya dipetakan jika Terima cocok dengan salah satu jenis media ini. Ekspresi dapat dinegasikan dengan menggunakan operator"! ", Seperti pada "! text / plain", yang cocok dengan semua permintaan dengan Accept selain "text / plain"
Oleksandr_DJ
@CharlieWu Ada masalah dengan tautannya
Matt
10

Saya memerangi masalah ini baru-baru ini dan menemukan jawaban yang jauh lebih baik tersedia di Spring 3.1:

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

Jadi, semudah JAX-RS sama seperti semua komentar menunjukkan itu bisa / seharusnya.

dbyoung
sumber
Layak porting ke Spring 3.1 untuk!
young.fu.panda
5
@dbyoung Itu sepertinya tidak benar, javadoc untuk producesmengatakan: "... permintaan hanya dipetakan jika Content-Type cocok dengan salah satu dari jenis media ini." yang berarti AFAIK bahwa producesrelevan dengan apakah metode tersebut cocok dengan permintaan dan bukan bagaimana jenis konten yang harus respons.
Ittai
@Ittai benar! "menghasilkan" menentukan apakah metode ini cocok dengan permintaan, tetapi BUKAN jenis konten apa dalam respons. sesuatu yang lain harus melihat "menghasilkan" ketika menentukan jenis konten apa yang akan diatur
anton1980
6

Anda dapat menggunakan menghasilkan untuk menunjukkan jenis respons yang Anda kirim dari pengontrol. Kata kunci "menghasilkan" ini akan sangat berguna dalam permintaan ajax dan sangat membantu dalam proyek saya

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

public @ResponseBody String getMobileData() {

}
Balasubramanian Jayaraman
sumber
4

Terima kasih digz6666, solusi Anda berfungsi untuk saya dengan sedikit perubahan karena saya menggunakan json:

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

Jawaban yang diberikan oleh axtavt (yang Anda rekomendasikan) tidak akan bekerja untuk saya. Bahkan jika saya telah menambahkan jenis media yang benar:

if (konv instance dari StringHttpMessageConverter) {                   
                    ((StringHttpMessageConverter) conv) .setSupportedMediaTypes (
                        Arrays.asList (
                                MediaType baru ("text", "html", Charset.forName ("UTF-8")),
                                MediaType baru ("aplikasi", "json", Charset.forName ("UTF-8")))));
                }
redochka
sumber
4

Saya mengatur tipe konten di MarshallingView di kacang ContentNegotiatingViewResolver . Ini bekerja dengan mudah, bersih dan lancar:

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

Saya menggunakan CharacterEncodingFilter, dikonfigurasi di web.xml. Mungkin itu membantu.

    <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
sumber
1
Ini hanya memfilter karakter dalam permintaan, bukan sebagai respons - Saya sudah menggunakan ini
Hurda
@ Burda: Dengan forceEncoding=trueitu menyaring respons juga, tetapi tidak akan membantu dalam kasus ini.
axtavt
Jawaban terbaik dan lebih cepat sejauh ini. Saya juga sudah mendeklarasikan dan menggunakan filter ini, tetapi dengan forceEncoding=false. Saya hanya mengaturnya falsedan "charset = UTF-8" berhasil ditambahkan ke Content-Typeheader.
Saad Benbouzid
2

jika tidak ada yang di atas bekerja untuk Anda mencoba untuk membuat permintaan ajax pada "POST" bukan "MENDAPATKAN", itu bekerja dengan baik untuk saya ... tidak ada di atas yang berhasil. Saya juga memiliki characterEncodingFilter.

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

Setelah saya mencoba banyak solusi untuk masalah ini .. Saya pikir ini berhasil dan baik-baik saja.

Szilard Jakab
sumber
2

Cara sederhana untuk menyelesaikan masalah ini di Spring 3.1.1 adalah: tambahkan kode konfigurasi berikut di 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>

Tidak perlu mengganti atau mengimplementasikan apa pun.

AdaroMu
sumber
2

jika Anda memutuskan untuk memperbaiki masalah ini melalui konfigurasi berikut:

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

Anda harus mengonfirmasi bahwa hanya ada satu mvc: tag yang digerakkan oleh anotasi di semua file * .xml Anda. jika tidak, konfigurasi mungkin tidak efektif.

Lich
sumber
1

Menurut tautan "Jika pengkodean karakter tidak ditentukan, spesifikasi Servlet mensyaratkan bahwa pengkodean ISO-8859-1 digunakan". Jika Anda menggunakan pegas 3.1 atau yang lebih baru gunakan konfigurasi penebangan untuk mengatur charset = UTF-8 ke response body
@RequestMapping (value = "url pemetaan Anda", menghasilkan = "teks / polos; charset = UTF-8")

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

Konfigurasi sampel:

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