Кто устанавливает тип содержимого ответа в Spring MVC (@ResponseBody)

126

У меня есть управляемое аннотациями веб-приложение Spring MVC Java, запущенное на веб-сервере причала (в настоящее время в плагине maven jetty).

Я пытаюсь реализовать некоторую поддержку AJAX с помощью одного метода контроллера, возвращающего только текст справки String. Ресурсы находятся в кодировке UTF-8, как и строка, но мой ответ с сервера идет с

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

даже когда мой браузер отправляет

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

Я как-то использую конфигурацию Spring по умолчанию

Я нашел подсказку добавить этот 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;
}
Hurda
источник

Ответы:

59

StringHttpMessageConverterНедостаточно простого объявления bean-компонента, вам нужно ввести его в 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 " />
axtavt
источник
10
Похоже на грязный взлом. Мне это не нравится, но использовать. Разработчики Spring Framework должны работать над этим случаем!
digz6666
Куда идет строка <bean class = "EncodingPostProcessor" />?
zod
1
@zod: In DispatcherServlet's config ( ...-servlet.xml)
axtavt
Спасибо. Кажется, это игнорируется. Мы используем mvc (я думаю), и у нас есть класс с атрибутом @Controller, который, похоже, является точкой входа. Этот класс больше нигде не упоминается (у него есть интерфейс с похожим именем), но он создается и вызывается правильно. Пути отображаются с помощью атрибута @RequestMapping. Мы не можем контролировать тип содержимого ответа (нам нужен xml). Как вы, наверное, догадались, я понятия не имею, что делаю, а разработчик, создавший это, покинул мою компанию. Спасибо.
zod
3
Как говорит @ digz6666, это грязный взлом. Spring должен увидеть, как это делает JAX-RS.
Адам Гент
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"/>, что в любом случае устарело в Spring 3.2 ...)
Jonik
может ли это создать application / xml, если так аннотировано?
Hurda
2
@Hurda: Очевидно, вы можете указать любой желаемый тип контента, изменив значение producesатрибута.
Jonik
1
Существует также MediaType.APPLICATION_JSON_VALUE для "application / json".
dev
2
Для UTF-8 см MediaType.APPLICATION_JSON_UTF8_VALUE..
calvinf 06
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 определить bean-компонент StringHttpMessageConverter. Этот ответ используется для установки типов содержимого ответа для всех сервлетов. Если вам просто нужно установить тип содержимого ответа для конкретного метода контроллера, используйте вместо него ответ 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 лучше, чем это.

digz6666
источник
Это также решение, если вы получаете ошибку the manifest may not be valid or the file could not be opened.в IE 11. Спасибо, digz!
Арун Кристофер
21

в RequestMapping можно добавить productions = "text / plain; charset = UTF-8"

@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
Этот код не компилируется; вы возвращаете что-то из метода void.
Эндрю Свон
2
извините, плохая ошибка, теперь она исправлена
Чарли Ву
3
Это неправильный ответ. Согласно Spring docs: производимые типы мультимедиа сопоставленного запроса, сужающие основное сопоставление. Формат представляет собой последовательность типов мультимедиа ("текст / простой", "приложение / *), причем запрос отображается только в том случае, если Accept соответствует одному из этих типов мультимедиа. Выражения могут быть инвертированы с помощью оператора"! ", Как в «! text / plain», который соответствует всем запросам с Accept, кроме «text / plain».
Oleksandr_DJ
@CharlieWu Проблема со ссылкой
Мэтт
10

Недавно я боролся с этой проблемой и нашел гораздо лучший ответ, доступный в Spring 3.1:

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

Итак, так же просто, как JAX-RS, как и все комментарии, которые указывали, что это может / должно быть.

dbyoung
источник
Стоит портировать на Spring 3.1 для!
young.fu.panda
5
@dbyoung Это кажется неправильным, javadoc для producesговорит: «... запрос отображается, только если Content-Type соответствует одному из этих типов мультимедиа». это означает, что AFAIK producesимеет отношение к тому, соответствует ли метод запросу, а не к тому, какой тип содержимого должен иметь ответ.
Ittai
@Ittai правильно! "производит" определяет, соответствует ли метод запросу, но НЕ какой тип содержимого содержится в ответе. что-то еще должно быть
обращено
6

Вы можете использовать производит, чтобы указать тип ответа, который вы отправляете от контроллера. Это ключевое слово "производит" будет наиболее полезно в запросе ajax и очень помогло в моем проекте.

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

public @ResponseBody String getMobileData() {

}
Баласубраманиан Джаяраман
источник
4

Спасибо digz6666, ваше решение работает для меня с небольшими изменениями, потому что я использую json:

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

Ответ, данный axtavt (который вы рекомендовали), не сработает для меня. Даже если я добавил правильный тип носителя:

if (conv instanceof StringHttpMessageConverter) {                   
                    ((StringHttpMessageConverter) conv) .setSupportedMediaTypes (
                        Arrays.asList (
                                новый MediaType ("текст", "html", Charset.forName ("UTF-8")),
                                новый MediaType ("приложение", "json", Charset.forName ("UTF-8"))));
                }
redochka
источник
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 01
@Hurda: forceEncoding=trueОн тоже фильтрует ответ, но в данном случае это не поможет.
axtavt 01
На данный момент лучший и более быстрый ответ. Я тоже уже объявлял и использовал этот фильтр, но с forceEncoding=false. Я просто установил его, falseи "charset = UTF-8" успешно добавлен в Content-Typeзаголовок.
Саад Бенбузид
2

если ничего из вышеперечисленного не сработало, вы попытаетесь сделать запросы ajax на «POST», а не на «GET», это сработало для меня хорошо ... ничего из вышеперечисленного не сделал. Еще у меня есть characterEncodingFilter.

Marius
источник
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>

Не нужно ничего переопределять или реализовывать.

AdaroMu
источник
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: annotation. в противном случае конфигурация может оказаться неэффективной.

Lich
источник
1

Согласно ссылке «Если кодировка символов не указана, спецификация сервлета требует, чтобы использовалась кодировка ISO-8859-1». Если вы используете Spring 3.1 или более позднюю версию, используйте следующую конфигурацию, чтобы установить charset = UTF-8 на тело ответа
@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>
Игорь Костомин
источник