Как изменить тип содержимого в обработчике исключений

Предположим, у меня есть контроллер, который служитGET запрашивает и возвращает bean-компонент для сериализации в JSON, а также предоставляет обработчик исключений дляIllegalArgumentException что можно поднять в сервисе:

@RequestMapping(value = "/meta/{itemId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public MetaInformation getMetaInformation(@PathVariable int itemId) {
    return myService.getMetaInformation(itemId);
}

@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public String handleIllegalArgumentException(IllegalArgumentException ex) {
    return ExceptionUtils.getStackTrace(ex);
}

Конверторы сообщений:

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
        <bean class="org.springframework.http.converter.StringHttpMessageConverter" />
    </mvc:message-converters>
</mvc:annotation-driven>

Теперь, когда я запрашиваю указанный URL в браузере, я вижу правильный ответ JSON. Однако, если возникает исключение, строковое исключение также преобразуется в JSON, но я бы хотел, чтобы его обрабатывалStringHttpMessageConverter (в результатеtext/plain тип пантомимы). Как я могу это сделать?

Чтобы сделать изображение более полным (и сложным), предположим, что у меня также есть следующий обработчик:

@RequestMapping(value = "/version", method = RequestMethod.GET)
@ResponseBody
public String getApplicationVersion() {
    return "1.0.12";
}

Этот обработчик позволяет возвращать строку, возвращаемуюMappingJackson2HttpMessageConverter а такжеStringHttpMessageConverter в зависимости от пройденногоAccept-type клиентом. Возвращаемые типы и значения должны быть следующими:

+----+---------------------+-----------------------+------------------+-------------------------------------+
| NN | URL                 | Accept-type           | Content-type     | Message converter                   |
|    |                     | request header        | response header  |                                     |
+----+---------------------+-----------------------+------------------+-------------------------------------+
| 1. | /version            | text/html; */*        | text/plain       | StringHttpMessageConverter          |
| 2. | /version            | application/json; */* | application/json | MappingJackson2HttpMessageConverter |
| 3. | /meta/1             | text/html; */*        | application/json | MappingJackson2HttpMessageConverter |
| 4. | /meta/1             | application/json; */* | application/json | MappingJackson2HttpMessageConverter |
| 5. | /meta/0 (exception) | text/html; */*        | text/plain       | StringHttpMessageConverter          |
| 6. | /meta/0 (exception) | application/json; */* | text/plain       | StringHttpMessageConverter          |
+----+---------------------+-----------------------+------------------+-------------------------------------+
 Simon Logic06 июн. 2018 г., 18:14
Я хотел бы отметить, что вы добавляете или переопределяетеexisitng конвертеры сообщений. Чтобы полностью заменить стандартные, используйте атрибут «register-defaults = false».

Ответы на вопрос(2)

Решение Вопроса

produces = MediaType.APPLICATION_JSON_VALUE из@RequestMapping изgetMetaInformation даст вам желаемый результат.

Тип ответа будет согласован в соответствии со значением типа содержимого в заголовке Accept.

редактировать

Поскольку это не распространяется на сценарий 3,4, вот решение, работающее сResponseEntity.class непосредственно:

@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleIllegalArgumentException(Exception ex) {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.TEXT_PLAIN);
    return new ResponseEntity<String>(ex.getMessage(), headers, HttpStatus.BAD_REQUEST);
}
 dma_k23 окт. 2012 г., 12:52
Спасибо за подсказку сResponseEntity! Как насчет настройкиsupportedMediaTypes собственность дляStringHttpMessageConverter (видетьмой ответ)? Может быть решением также.
 dma_k19 окт. 2012 г., 19:09
Звучит хорошо. Но как будут обрабатываться сценарии (3,4)?
 dma_k23 окт. 2012 г., 16:43
Я только что проверил ваше решение сResponseEntity: это не работает. Тип содержимого переопределяется конвертером сообщений, а конвертер сообщений выбирается (грубые алгоритмы) путем пересеченияAccept-type и конверторsupportedMediaTypes.
 oehmiche23 окт. 2012 г., 18:21
Я успешно протестировал его в Spring 3.1.2 на tcServer v2.7. - Вы использовали конфигурацию пружины, упомянутую в вашем вопросе?

StringHttpMessageConverter добавляет универсальный тип пантомимы*/* к списку поддерживаемых типов носителей, в то время какMappingJackson2HttpMessageConverter связан сapplication/json только.когда@RequestMapping обеспечиваетproduces = ...это значение хранится вHttpServletRequest (видетьRequestMappingInfoHandlerMapping.handleMatch()) и когда вызывается обработчик ошибок, этот тип MIME автоматически наследуется и используется.

Решение в простом случае было бы поставитьStringHttpMessageConverter первый в списке:

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes">
                <array>
                    <util:constant static-field="org.springframework.http.MediaType.TEXT_PLAIN" />
                </array>
            </property>
        </bean>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
    </mvc:message-converters>
</mvc:annotation-driven>

а также удалитьproduces из@RequestMapping аннотация:

@RequestMapping(value = "/meta/{itemId}", method = RequestMethod.GET)
@ResponseBody
public MetaInformation getMetaInformation(@PathVariable int itemId) {
    return myService.getMetaInformation(itemId);
}

В настоящее время:

StringHttpMessageConverter откажется от всех типов, которые толькоMappingJackson2HttpMessageConverter может справиться (MetaInformation, java.util.Collectionи т. д.) позволяя передавать их дальше.В случае исключения по сценарию (5, 6)StringHttpMessageConverter будет иметь приоритет.

Пока все хорошо, но, к сожалению, все становится сложнее сObjectToStringHttpMessageConverter, Для типа возврата обработчикаjava.util.Collection<MetaInformation> этот конвертер сообщений сообщит, что может преобразовать этот тип вjava.lang.String, Ограничение связано с тем, что типы элементов коллекции стираются иAbstractHttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType) метод получаетjava.util.Collection<?> класс для проверки, однако, когда дело доходит до шага преобразованияObjectToStringHttpMessageConverter выходит из строя. Для решения проблемы мы продолжаемproduces за@RequestMapping аннотация, в которой должен использоваться JSON-конвертер, но для принудительного определения корректного типа контента для обработчика исключений мы удалимHandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE атрибут изHttpServletRequest:

@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public String handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException ex) {
    request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    return ExceptionUtils.getStackTrace(ex);
}

@RequestMapping(value = "/meta", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Collection<MetaInformation> getMetaInformations() {
    return myService.getMetaInformations();
}

Контекст остается таким же, каким он был изначально:

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
        <bean class="org.springframework.http.converter.ObjectToStringHttpMessageConverter">
            <property name="conversionService">
                <bean class="org.springframework.context.support.ConversionServiceFactoryBean" />
            </property>
            <property name="supportedMediaTypes">
                <array>
                    <util:constant static-field="org.springframework.http.MediaType.TEXT_PLAIN" />
                </array>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

Теперь сценарии (1,2,3,4) обрабатываются правильно из-за согласования типа контента, а сценарии (5,6) обрабатываются в обработчике исключений.

В качестве альтернативы можно заменить тип возврата коллекции на массивы, тогда снова применимо решение № 1:

@RequestMapping(value = "/me,ta", method = RequestMethod.GET)
@ResponseBody
public MetaInformation[] getMetaInformations() {
    return myService.getMetaInformations().toArray();
}

Для обсуждения:

я думаю чтоAbstractMessageConverterMethodProcessor.writeWithMessageConverters() не должен наследовать класс от значения, а скорее от сигнатуры метода:

Type returnValueType = returnType.getGenericParameterType();

а такжеHttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType) следует изменить на:

canWrite(Type returnType, MediaType mediaType)

или (в случае, если это слишком ограничивает потенциальные преобразователи на основе классов)

canWrite(Class<?> valueClazz, Type returnType, MediaType mediaType)

Тогда параметризованные типы могут быть обработаны правильно, и решение № 1 снова будет применимо.

 Shadow Man20 окт. 2017 г., 00:36
Спасибо! Это ответ, который работал для меня. Конкретно линияrequest.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); или расширениемrequest.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.APPLICATION_JSON));

Ваш ответ на вопрос