ใครกำหนดประเภทเนื้อหาการตอบกลับใน Spring MVC (@ResponseBody)


126

ฉันมีแอปพลิเคชันเว็บ Spring MVC Java ที่ใช้ Annotation ซึ่งทำงานบนเว็บเซิร์ฟเวอร์ของท่าเทียบเรือ (ปัจจุบันอยู่ในปลั๊กอินท่าเทียบเรือ maven)

ฉันกำลังพยายามสนับสนุน AJAX ด้วยวิธีการควบคุมเดียวที่ส่งคืนข้อความช่วยเหลือเพียงสตริง ทรัพยากรอยู่ในการเข้ารหัส UTF-8 ดังนั้นสตริงก็เช่นกัน แต่การตอบสนองของฉันจากเซิร์ฟเวอร์มาพร้อมกับ

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

แม้ว่าเบราว์เซอร์ของฉันจะส่ง

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

ฉันใช้การกำหนดค่าเริ่มต้นของสปริง

ฉันพบคำใบ้ในการเพิ่ม bean นี้ในการกำหนดค่า แต่ฉันคิดว่ามันไม่ได้ใช้เพราะมันบอกว่ามันไม่รองรับการเข้ารหัสและใช้ค่าเริ่มต้นแทน

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

รหัสคอนโทรลเลอร์ของฉันคือ (โปรดทราบว่าการเปลี่ยนแปลงประเภทการตอบกลับนี้ใช้ไม่ได้สำหรับฉัน):

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

คำตอบ:


59

การประกาศอย่างง่ายของStringHttpMessageConverterถั่วนั้นไม่เพียงพอคุณต้องฉีดเข้าไปใน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>

อย่างไรก็ตามการใช้วิธีนี้คุณจะต้อง redefine ทั้งหมดHttpMessageConverters <mvc:annotation-driven />และยังจะไม่ได้ทำงานกับ

ดังนั้นวิธีที่สะดวกที่สุด แต่น่าเกลียดที่สุดคือการสกัดกั้นการสร้างอินสแตนซ์AnnotationMethodHandlerAdapterด้วย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
ดูเหมือนเป็นการแฮ็กสกปรก ฉันไม่ชอบ แต่ใช้ นักพัฒนา Spring framework ควรดำเนินการในกรณีนี้!
digz6666

บรรทัด <bean class = "EncodingPostProcessor" /> ไปไหน
zod

1
@zod: ในDispatcherServletconfig ( ...-servlet.xml)
axtavt

ขอบคุณ ดูเหมือนจะไม่สนใจ เราใช้ mvc (ฉันคิดว่า) และเรามีคลาสที่มีแอตทริบิวต์ @Controller ซึ่งดูเหมือนจะเป็นจุดเริ่มต้น คลาสนี้ไม่ได้กล่าวถึงที่อื่น (มีอินเทอร์เฟซที่มีชื่อคล้ายกัน) แต่มันถูกสร้างอินสแตนซ์และเรียกอย่างถูกต้อง เส้นทางถูกแมปด้วยแอตทริบิวต์ @RequestMapping เราไม่สามารถควบคุมประเภทเนื้อหาของการตอบกลับได้ (เราต้องการ xml) อย่างที่คุณบอกได้ฉันไม่รู้ว่าฉันกำลังทำอะไรอยู่และนักพัฒนาที่สร้างสิ่งนี้ได้ออกจาก บริษัท ของฉันแล้ว ขอบคุณ
zod

3
ตามที่ @ digz6666 กล่าวว่านี่เป็นการแฮ็กที่สกปรก Spring ควรดูว่า JAX-RS ทำได้อย่างไร
Adam Gent

166

ฉันพบวิธีแก้ปัญหาสำหรับ Spring 3.1 ด้วยการใช้คำอธิบายประกอบ @ResponseBody นี่คือตัวอย่างของคอนโทรลเลอร์ที่ใช้เอาต์พุต Json:

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

}

7
+1 สิ่งนี้แก้ไขให้ฉันด้วย แต่หลังจากที่ฉันเปลี่ยนไปใช้<mvc:annotation-driven/>ใน applicationContext (แทนที่จะ<bean class=" [...] DefaultAnnotationHandlerMapping"/>เลิกใช้ในฤดูใบไม้ผลิ 3.2 ต่อไป ... )
Jonik

ca สิ่งนี้สร้าง application / xml ถ้าใส่คำอธิบายประกอบด้วยวิธีนี้?
Hurda

2
@Hurda: แน่นอนคุณสามารถระบุประเภทเนื้อหาที่คุณต้องการได้โดยเปลี่ยนค่าของproducesแอตทริบิวต์
Jonik

1
มี MediaType.APPLICATION_JSON_VALUE สำหรับ "application / json" เช่นกัน
dev

2
สำหรับ UTF-8 MediaType.APPLICATION_JSON_UTF8_VALUEดู
calvinf

51

โปรดทราบว่าใน Spring MVC 3.1 คุณสามารถใช้เนมสเปซ MVC เพื่อกำหนดค่าตัวแปลงข้อความ:

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

หรือการกำหนดค่าตามรหัส:

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

การเรียงลำดับของงานยกเว้นว่า 1) ก่อให้เกิดการตอบสนองด้วยAccept-Charsetส่วนหัวที่อาจแสดงรายการการเข้ารหัสอักขระที่รู้จักทั้งหมดและ 2) เมื่อคำขอมีAcceptส่วนหัวsupportedMediaTypesคุณสมบัติของตัวแปลงจะไม่ถูกใช้ดังนั้นตัวอย่างเช่นเมื่อฉันพิมพ์คำขอ โดยตรง URL ในเบราว์เซอร์การตอบสนองจะมีContent-Type: text/htmlส่วนหัวแทน
Giulio Piancastelli

3
คุณสามารถทำให้ง่ายขึ้นเนื่องจาก "text / plain" เป็นค่าเริ่มต้นต่อไป: <bean class="org.springframework.http.converter.StringHttpMessageConverter"><constructor-arg value="UTF-8" /></bean>
Igor Mukhin

คำตอบนี้ควรได้รับการยอมรับว่าเป็นคำตอบที่ถูกต้อง นอกจากนี้วิธีการของ @IgorMukhin ในการกำหนด StringHttpMessageConverter bean ทำงานได้ คำตอบนี้ใช้เพื่อตั้งค่าชนิดเนื้อหาการตอบกลับสำหรับ servlets ทั้งหมด หากคุณต้องการตั้งค่าประเภทเนื้อหาการตอบกลับสำหรับวิธีการควบคุมเฉพาะให้ใช้คำตอบของ Warrior แทน (ใช้สร้างอาร์กิวเมนต์ใน @RequestMapping)
PickBoy

3
@GiulioPiancastelli คำถามแรกของคุณสามารถแก้ไขได้โดยเพิ่ม <property name = "writeAcceptCharset" value = "false" /> ลงใน bean
PickBoy

44

ในกรณีที่คุณสามารถตั้งค่าการเข้ารหัสได้ด้วยวิธีต่อไปนี้:

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

ฉันคิดว่าการใช้ StringHttpMessageConverter ดีกว่านี้


นี่เป็นวิธีแก้ปัญหาหากคุณได้รับข้อผิดพลาดthe manifest may not be valid or the file could not be opened.ใน IE 11 ขอบคุณ digz!
อรุณคริสโตเฟอร์

21

คุณสามารถเพิ่ม produce = "text / plain; charset = UTF-8" ไปยัง 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);
}

ดูบล็อกนี้เพื่อดูรายละเอียดเพิ่มเติม


2
รหัสนั้นจะไม่รวบรวม คุณกำลังส่งคืนบางสิ่งจากวิธีโมฆะ
Andrew Swan

2
ขออภัยข้อผิดพลาดที่ไม่ดีได้รับการแก้ไขแล้ว
Charlie Wu

3
มันเป็นคำตอบที่ไม่ถูกต้อง ตามเอกสารสปริง: ชนิดสื่อที่ผลิตได้ของคำขอที่แมปซึ่งจะ จำกัด การแมปหลักให้แคบลง รูปแบบเป็นลำดับของประเภทสื่อ ("text / plain", "application / *) โดยจะมีการแมปคำขอเฉพาะเมื่อคำว่า Accept ตรงกับสื่อประเภทใดประเภทหนึ่งเหล่านี้นิพจน์สามารถลบล้างได้โดยใช้ตัวดำเนินการ"! "เช่นเดียวกับใน "! text / plain" ซึ่งจับคู่คำขอทั้งหมดกับยอมรับนอกเหนือจาก "text / plain"
Oleksandr_DJ

@CharlieWu มีปัญหากับลิงค์
Matt

10

เมื่อเร็ว ๆ นี้ฉันกำลังต่อสู้กับปัญหานี้และพบคำตอบที่ดีกว่ามากใน Spring 3.1:

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

ดังนั้นง่ายเหมือน JAX-RS เช่นเดียวกับความคิดเห็นทั้งหมดที่ระบุว่าสามารถ / ควรจะเป็น


คุ้มที่จะย้ายไป Spring 3.1 สำหรับ!
young.fu.panda

5
@dbyoung ดูเหมือนจะไม่ถูกต้อง javadoc สำหรับข้อความproducesระบุว่า: "... ขอให้แมปเฉพาะเมื่อ Content-Type ตรงกับประเภทสื่อเหล่านี้เท่านั้น" ซึ่งหมายถึง AFAIK ที่producesเกี่ยวข้องกับว่าวิธีการนั้นตรงกับคำขอหรือไม่และไม่ควรตอบสนองต่อเนื้อหาประเภทใด
Ittai

@ อิไตถูกต้อง! "ผลิต" กำหนดว่าวิธีการนั้นตรงกับคำขอหรือไม่ แต่ไม่ใช่ประเภทเนื้อหาที่อยู่ในการตอบกลับ อย่างอื่นต้องดูที่ "ผลิต" เมื่อกำหนดประเภทเนื้อหาที่จะตั้ง
anton1980

6

คุณสามารถใช้ผลิตเพื่อระบุประเภทของการตอบกลับที่คุณกำลังส่งจากคอนโทรลเลอร์ คำหลัก "สร้าง" นี้จะมีประโยชน์มากที่สุดในคำขอของ ajax และมีประโยชน์มากในโครงการของฉัน

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

public @ResponseBody String getMobileData() {

}

4

ขอบคุณ digz6666 โซลูชันของคุณใช้ได้กับฉันโดยมีการเปลี่ยนแปลงเล็กน้อยเพราะฉันใช้ json:

responseHeaders.add ("ประเภทเนื้อหา", "application / json; charset = utf-8");

คำตอบที่ได้รับจาก axtavt (สิ่งที่คุณแนะนำ) จะไม่ได้ผลสำหรับฉัน แม้ว่าฉันจะเพิ่มประเภทสื่อที่ถูกต้อง:

ถ้า (conv instanceof StringHttpMessageConverter) {                   
                    ((StringHttpMessageConverter) Conv) .setSupportedMediaTypes (
                        Arrays.asList (
                                MediaType ใหม่ ("text", "html", Charset.forName ("UTF-8")),
                                MediaType ใหม่ ("application", "json", Charset.forName ("UTF-8"))));
                }

4

ฉันตั้งค่าชนิดเนื้อหาใน MarshallingView ในContentNegotiatingViewResolver bean ทำงานได้ง่ายสะอาดและราบรื่น:

<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

ฉันใช้ CharacterEncodingFilter ซึ่งกำหนดค่าใน web.xml บางทีนั่นอาจช่วยได้

    <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
นี่เป็นเพียงตัวกรองตัวละครในคำขอไม่ใช่เพื่อตอบสนอง - ฉันใช้อันนี้หมดแล้ว
Hurda

@ ฮูร์ดา: ด้วยforceEncoding=trueมันจะกรองการตอบสนองด้วย แต่มันจะไม่ช่วยในกรณีนี้
axtavt

คำตอบที่ดีที่สุดและเร็วกว่า ฉันก็ประกาศและใช้ตัวกรองนี้แล้ว แต่ด้วยforceEncoding=false. ฉันเพิ่งตั้งค่าเป็นfalseและเพิ่ม "charset = UTF-8" ในContent-Typeส่วนหัวเรียบร้อยแล้ว
Saad Benbouzid

2

หากวิธีการข้างต้นไม่ได้ผลสำหรับคุณพยายามที่จะส่งคำขอ ajax ใน "POST" ไม่ใช่ "GET" ซึ่งได้ผลดีสำหรับฉัน ... ฉันยังมี 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");
    }
}

หลังจากที่ฉันได้ลองวิธีแก้ปัญหามากมายสำหรับปัญหานี้แล้ว .. ฉันคิดออกแล้วและมันก็ใช้ได้ดี


2

วิธีง่ายๆในการแก้ปัญหานี้ใน Spring 3.1.1 คือ: เพิ่มรหัสการกำหนดค่าต่อไปนี้ใน 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>

ไม่จำเป็นต้องลบล้างหรือดำเนินการใด ๆ


2

หากคุณตัดสินใจที่จะแก้ไขปัญหานี้ผ่านการกำหนดค่าต่อไปนี้:

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

คุณควรยืนยันว่าควรมีแท็ก mvc: annotation-driven เพียงแท็กเดียวในไฟล์ * .xml ทั้งหมดของคุณ มิฉะนั้นการกำหนดค่าอาจไม่มีผล


1

ตามลิงค์ "หากไม่ได้ระบุการเข้ารหัสอักขระข้อกำหนดของ Servlet กำหนดให้ใช้การเข้ารหัส ISO-8859-1" หากคุณใช้สปริง 3.1 หรือใหม่กว่าให้ใช้การกำหนดค่า fallowing เพื่อตั้งค่า charset = UTF-8 เป็น 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;
        }
    }
}

การกำหนดค่าตัวอย่าง:

    <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>
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.