Хто встановлює тип вмісту відповідей у ​​Spring MVC (@ResponseBody)


126

У мене в веб-застосунку Spring MVC Java запускається Анотація на веб-сервері jetty (зараз у плагіні Maven jetty).

Я намагаюся зробити підтримку AJAX одним методом контролера, повертаючи лише String help text. Ресурси містяться в кодуванні UTF-8 і так само є рядком, але приходить моя відповідь від сервера

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

навіть коли мій браузер надсилає

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

Я використовую якось конфігурацію весни за замовчуванням

Я знайшов підказку, щоб додати цей бін до конфігурації, але я думаю, що він просто не використовується, оскільки він говорить, що він не підтримує кодування, а використовується замість нього.

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

Однак, використовуючи цей метод, вам доведеться переосмислити всі 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
Це здається брудним злом. Мені це не подобається, але використовувати. Весняні розробники рамок повинні працювати над цим випадком!
digz6666

Куди йде рядок <bean class = "EncodingPostProcessor" />?
зод

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

Дякую. Схоже, це ігнорується. Ми використовуємо mvc (я думаю) і у нас є клас з атрибутом @Controller, який, здається, є точкою входу. Клас ніде більше не згадується (у нього є інтерфейс з подібною назвою), але він створений і викликається правильно. Шляхи відображаються з атрибутом @RequestMapping. Ми не можемо контролювати тип вмісту відповіді (нам потрібен xml). Як ви, напевно, можете сказати, я поняття не маю, чим я займаюся, а розробник, який це створив, покинув мою компанію. Дякую.
zod

3
Як говорить @ digz6666, це брудний злом. Весна повинна побачити, як це робить JAX-RS.
Адам Гент

166

Я знайшов рішення для весни 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 це створює додаток / xml, якщо зазначено таким чином?
Хурда

2
@Hurda: Очевидно, ви можете вказати будь-який тип вмісту, який хочете, змінивши значення producesатрибута.
Джонік

1
Існує MediaType.APPLICATION_JSON_VALUE, також для "application / json".
дев

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замість цього має заголовок.
Джуліо Піанкастеллі

3
Ви можете спростити, оскільки "текст / звичайний" за замовчуванням все одно: <bean class="org.springframework.http.converter.StringHttpMessageConverter"><constructor-arg value="UTF-8" /></bean>
Ігор Мухін

Цю відповідь слід сприймати як правильну відповідь. Також спосіб @IgorMukhin визначити StringHttpMessageConverter працює. Ця відповідь використовується для встановлення типів вмісту відповідей для всіх сервлетів. Якщо вам просто потрібно встановити тип вмісту відгуку для конкретного методу контролера, використовуйте натомість відповідь Warrior (використовуйте параметри в @RequestMapping)
PickBoy

3
@GiulioPiancastelli ваше перше питання можна вирішити, додавши <be name = = "writeAcceptCharset" значення = "false" /> в бін
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
Цей код не збирається; ви повертаєте щось із недійсного методу.
Ендрю Лебедь

2
вибачте, погана помилка, це виправлено зараз
Чарлі Ву

3
Це неправильна відповідь. Відповідно до весняних документів: продуктивні типи носіїв відображеного запиту, звужуючи первинне відображення. Формат - це послідовність типів медіа ("текст / звичайний", "додаток / *), із запитом, відображеним лише у випадку, якщо Accept відповідає одному з цих типів медіа. Вирази можна заперечити за допомогою оператора"! ", Як у "! text / plain", який відповідає всім запитам, крім Accept, окрім "text / plain".
Олександр_DJ

@CharlieWu Існує проблема із посиланням
Метт

10

Я нещодавно боровся з цим питанням і знайшов набагато кращу відповідь навесні 3.1:

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

Отже, настільки ж просто, як і JAX-RS, як і всі коментарі, які вказували, що це може / повинно бути.


Варто перенести до Весни 3.1 для!
young.fu.panda

5
@dbyoung Це не здається правильним, javadoc для produces словами: "... запит відображається лише в тому випадку, якщо тип вмісту відповідає одному з цих типів медіа". що означає 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 (що ти рекомендував), не буде працювати для мене. Навіть якщо я додав правильний тип носія:

if (conv instance of StringHttpMessageConverter) {                   
                    ((StringHttpMessageConverter) conv) .setSupportedMediaTypes (
                        Arrays.asList (
                                новий MediaType ("текст", "html", Charset.forName ("UTF-8")),
                                новий MediaType ("додаток", "json", Charset.forName ("UTF-8"))));
                }

4

Я встановлюю тип вмісту в MarshallingView в бобі ContentNegotiatingViewResolver . Працює легко, чисто і плавно:

<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заголовка.
Саад Бенбузід,

2

якщо жодне з перерахованого вище не працювало для вас, ви намагаєтеся робити запити ajax на "POST" не "GET", це добре працювало для мене ... нічого з вищезазначеного не робив. У мене також є символEncodingFilter.


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

Простий спосіб вирішити цю проблему навесні 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>

ви повинні підтвердити, що у вашому * .xml-файлі має бути лише один тег mvc: анотація. в іншому випадку конфігурація може бути неефективною.


1

Відповідно до посилання "Якщо кодування символів не вказано, специфікація сервлета вимагає, щоб було використано кодування ISO-8859-1". Якщо ви використовуєте навесні 3.1 або пізнішої версії, використовуйте конфігурацію, що встановлюється для встановлення charset = UTF-8 на body
response @RequestMapping (value = "URL-адреса вашого відображення", виробляє = "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>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.