Ai đặt loại nội dung phản hồi trong Spring MVC (@ResponseBody)


126

Tôi đang có trong ứng dụng web Spring MVC Java được chú thích của mình chạy trên máy chủ web jetty (hiện đang có trong plugin maven jetty).

Tôi đang cố gắng thực hiện một số hỗ trợ AJAX với một phương thức điều khiển chỉ trả về văn bản trợ giúp Chuỗi. Tài nguyên ở dạng mã hóa UTF-8 và chuỗi cũng vậy, nhưng phản hồi của tôi từ máy chủ đi kèm với

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

ngay cả khi trình duyệt của tôi gửi

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

Tôi đang sử dụng cấu hình mặc định của mùa xuân

Tôi đã tìm thấy một gợi ý để thêm bean này vào cấu hình, nhưng tôi nghĩ rằng nó chỉ không được sử dụng, bởi vì nó nói rằng nó không hỗ trợ mã hóa và một cái mặc định được sử dụng thay thế.

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

Mã điều khiển của tôi là (lưu ý rằng thay đổi loại phản hồi này không hoạt động đối với tôi):

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

Câu trả lời:


59

Khai báo đơn giản về StringHttpMessageConverterđậu là không đủ, bạn cần tiêm nó vào 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>

Tuy nhiên, bằng cách sử dụng phương pháp này, bạn phải xác định lại tất cả HttpMessageConverter, và nó cũng không hoạt động <mvc:annotation-driven />.

Vì vậy, có lẽ phương pháp thuận tiện nhất nhưng xấu nhất là chặn ngay lập tức AnnotationMethodHandlerAdaptervới 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
Có vẻ như một hack bẩn. Tôi không thích nó nhưng để sử dụng. Các nhà phát triển khung mùa xuân nên làm việc trong trường hợp này!
digz6666

Dòng <bean class = "EncodingPostProcessor" /> đi đâu?
zod

1
@zod: Trong DispatcherServletcấu hình của ( ...-servlet.xml)
axtavt

Cảm ơn. Nó dường như bị bỏ qua. Chúng tôi đang sử dụng mvc (tôi nghĩ) và chúng tôi có một lớp với thuộc tính @Controll, dường như là điểm vào. Lớp này không được đề cập ở bất kỳ nơi nào khác (nó có giao diện với tên tương tự) nhưng nó được khởi tạo và gọi đúng. Các đường dẫn được ánh xạ với thuộc tính @RequestMapping. Chúng tôi không thể kiểm soát loại nội dung của phản hồi (chúng tôi cần xml). Như bạn có thể nói, tôi không biết tôi đang làm gì và nhà phát triển đã tạo ra công ty này đã rời khỏi công ty của tôi. Cảm ơn.
zod

3
Như @ digz6666 nói đây là một bản hack bẩn. Mùa xuân sẽ thấy cách JAX-RS thực hiện nó.
Adam Gent

166

Tôi tìm thấy giải pháp cho mùa xuân 3.1. với việc sử dụng chú thích @ResponseBody. Dưới đây là ví dụ về bộ điều khiển sử dụng đầu ra Json:

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

}

7
+1. Điều này cũng giải quyết nó cho tôi, nhưng chỉ sau khi tôi chuyển sang sử dụng <mvc:annotation-driven/>trong applicationContext. (Thay vì <bean class=" [...] DefaultAnnotationHandlerMapping"/>, không được dùng trong Mùa xuân 3.2 ...)
Jonik

ca này sản xuất ứng dụng / xml nếu chú thích theo cách này?
Hurda

2
@Hurda: Rõ ràng bạn có thể chỉ định bất kỳ loại nội dung nào bạn muốn bằng cách thay đổi giá trị của producesthuộc tính.
Jonik

1
Có một MediaType.APPLICATION_JSON_VALUE, cho "application / json".
dev

2
Đối với UTF-8, xem MediaType.APPLICATION_JSON_UTF8_VALUE.
calvinf

51

Lưu ý rằng trong Spring MVC 3.1, bạn có thể sử dụng không gian tên MVC để định cấu hình trình chuyển đổi thông báo:

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

Hoặc cấu hình dựa trên mã:

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

Sắp xếp các tác phẩm, ngoại trừ 1) nó gây ô nhiễm phản hồi bằng một Accept-Charsettiêu đề có thể liệt kê mọi mã hóa ký tự đã biết và 2) khi yêu cầu có Accepttiêu đề, thuộc supportedMediaTypestính của trình chuyển đổi không được sử dụng , ví dụ như khi tôi thực hiện gõ yêu cầu trực tiếp URL trong trình duyệt, phản hồi có Content-Type: text/htmltiêu đề thay thế.
Giulio Piancastelli

3
Dù sao bạn cũng có thể đơn giản hóa vì "văn bản / văn bản" là mặc định: <bean class="org.springframework.http.converter.StringHttpMessageConverter"><constructor-arg value="UTF-8" /></bean>
Igor Mukhin

Câu trả lời này nên được chấp nhận là câu trả lời đúng. Ngoài ra, cách của @IgorMukhin để xác định các chuỗi bean StringHttpMessageConverter. Câu trả lời này được sử dụng để đặt loại nội dung phản hồi cho tất cả các máy chủ. Nếu bạn chỉ cần đặt loại nội dung phản hồi cho một phương thức điều khiển cụ thể, thay vào đó hãy sử dụng câu trả lời của Chiến binh (sử dụng tạo đối số trong @RequestMapping)
PickBoy

3
@GiulioPiancastelli câu hỏi đầu tiên của bạn có thể được giải quyết bằng cách thêm <property name = "writeAcceptCharset" value = "false" /> vào bean
PickBoy

44

Chỉ trong trường hợp bạn cũng có thể đặt mã hóa theo cách sau:

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

Tôi nghĩ rằng sử dụng StringHttpMessageConverter tốt hơn thế này.


Đây cũng là giải pháp nếu bạn gặp lỗi the manifest may not be valid or the file could not be opened.trong IE 11. Cảm ơn digz!
Arun Christopher

21

bạn có thể thêm produc = "text / plain; charset = UTF-8" vào 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);
}

xem blog này để biết thêm chi tiết


2
Mã đó sẽ không biên dịch; bạn đang trả lại một cái gì đó từ một phương thức void.
Andrew Swan

2
xin lỗi lỗi xấu, nó đã được sửa ngay bây giờ
Charlie Wu

3
Đó là một câu trả lời không chính xác. Theo tài liệu mùa xuân: Các loại phương tiện có thể sản xuất của yêu cầu được ánh xạ, thu hẹp ánh xạ chính. Định dạng là một chuỗi các loại phương tiện ("văn bản / thuần túy", "ứng dụng / *), với một yêu cầu chỉ được ánh xạ nếu Chấp nhận khớp với một trong các loại phương tiện này. Biểu thức có thể được phủ định bằng cách sử dụng toán tử"! ", Như trong "! text / plain", phù hợp với tất cả các yêu cầu với Chấp nhận khác với "text / plain".
Oleksandr_DJ

@CharlieWu Có một vấn đề với liên kết
Matt

10

Tôi đã chiến đấu với vấn đề này gần đây và tìm thấy một câu trả lời tốt hơn nhiều có sẵn trong Mùa xuân 3.1:

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

Vì vậy, dễ dàng như JAX-RS giống như tất cả các ý kiến ​​cho thấy nó có thể / nên như vậy.


Đáng để chuyển sang mùa xuân 3.1 cho!
young.fu.panda

5
@dbyoung Điều đó có vẻ không đúng, javadoc cho producesbiết: "... yêu cầu chỉ được ánh xạ nếu Kiểu Nội dung khớp với một trong các loại phương tiện này." điều đó có nghĩa là AFAIK producescó liên quan đến việc phương thức đó có phù hợp với yêu cầu hay không và cách phản hồi nên có loại nội dung nào.
Ittai

@Ittai đúng rồi! "Sản xuất" xác định xem phương thức có khớp với yêu cầu không, nhưng KHÔNG phải loại nội dung nào trong phản hồi. một cái gì đó khác phải được xem xét "sản xuất" khi xác định loại nội dung sẽ được đặt
anton1980

6

Bạn có thể sử dụng sản xuất để chỉ ra loại phản hồi bạn đang gửi từ bộ điều khiển. Từ khóa "sản xuất" này sẽ hữu ích nhất trong yêu cầu ajax và rất hữu ích trong dự án của tôi

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

public @ResponseBody String getMobileData() {

}

4

Cảm ơn digz6666, giải pháp của bạn hoạt động với tôi với một chút thay đổi vì tôi đang sử dụng json:

answerHeaders.add ("Kiểu nội dung", "application / json; charset = utf-8");

Câu trả lời được đưa ra bởi axtavt (bạn đã khuyến nghị) sẽ không hiệu quả với tôi. Ngay cả khi tôi đã thêm loại phương tiện chính xác:

if (đối tượng của StringHttpMessageConverter) {                   
                    ((StringHttpMessageConverter) conv) .setSupportedMediaTypes (
                        Mảng.asList (
                                MediaType mới ("văn bản", "html", Charset.forName ("UTF-8")),
                                MediaType mới ("ứng dụng", "json", Charset.forName ("UTF-8"))));
                }

4

Tôi đặt loại nội dung trong MarshallingView trong bean ContentNegotiatingViewResolver . Nó hoạt động dễ dàng, sạch sẽ và trơn tru:

<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

Tôi đang sử dụng CharacterEncodingFilter, được định cấu hình trong tệp web.xml. Có lẽ điều đó giúp.

    <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
Điều này chỉ lọc nhân vật theo yêu cầu, không phản hồi - Tôi đã sẵn sàng sử dụng cái này
Hurda

@Hurda: Với forceEncoding=truenó cũng lọc phản hồi, nhưng nó sẽ không giúp ích gì trong trường hợp này.
axtavt

Câu trả lời tốt nhất và nhanh hơn cho đến nay. Tôi cũng đã khai báo và sử dụng bộ lọc này, nhưng với forceEncoding=false. Tôi vừa đặt nó thành false"charset = UTF-8" được thêm thành công vào Content-Typetiêu đề.
Saad Benbouzid

2

nếu không có cách nào ở trên làm việc cho bạn, hãy thử thực hiện các yêu cầu ajax trên "POST" chứ không phải "GET", điều đó có hiệu quả với tôi ... không có điều nào ở trên làm được. Tôi cũng có nhân vậtEncodingFilter.


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

Sau khi tôi đã cố gắng rất nhiều cách giải quyết cho vấn đề này .. tôi nghĩ điều này và nó hoạt động tốt.


2

Cách đơn giản để giải quyết vấn đề này trong Spring 3.1.1 là: thêm các mã cấu hình sau vào 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>

Không cần ghi đè hoặc thực hiện bất cứ điều gì.


2

nếu bạn quyết định khắc phục sự cố này thông qua cấu hình sau:

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

bạn nên xác nhận rằng chỉ nên có một thẻ mvc: chú thích điều khiển trong tất cả tệp * .xml của bạn. nếu không, cấu hình có thể không hiệu quả.


1

Theo liên kết "Nếu mã hóa ký tự không được chỉ định, thông số Servlet yêu cầu mã hóa ISO-8859-1 được sử dụng". Nếu bạn đang sử dụng spring 3.1 trở lên, hãy sử dụng cấu hình rơi để đặt charset = UTF-8 thành phản hồi cơ thể
@RequestMapping (value = "url ánh xạ của bạn", tạo = "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;
        }
    }
}

Cấu hình mẫu:

    <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>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.