2012-10-19 16 views
21

Załóżmy Mam kontroler, który służy GET żądania i zwraca Fasola być szeregowane do JSON, a także zapewnia obsługę wyjątków dla IllegalArgumentException które mogą być podniesione w służbie:Jak zmienić typ zawartości w obsługi wyjątków

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

konwertery wiadomości są:

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

teraz, kiedy zwrócić się do danego adresu URL w przeglądarce widzę prawidłową odpowiedź JSON. Jednakże, jeśli wyjątek zostanie podniesiony, sznurowany wyjątek jest konwertowany również na JSON, ale chciałbym, aby był przetwarzany przez StringHttpMessageConverter (co daje w wyniku typ MIME text/plain). Jak mogę to zrobić?

aby obraz bardziej kompletne (i skomplikowane), załóżmy, że mam również następujące Handler:

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

Ten uchwyt pozwala ciąg powrót do odcinkach zarówno MappingJackson2HttpMessageConverter i StringHttpMessageConverter zależności w upłynął Accept-type przez klient. Rodzaje i zwracane wartości powinny być następujące:

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

Odpowiedz

17

myślę usunięcie produces = MediaType.APPLICATION_JSON_VALUE z @RequestMapping z getMetaInformation daje pożądanego rezultatu.

Typ odpowiedzi będzie negocjowany zgodnie z wartością typu treści w nagłówku Accept.


edit

Ponieważ nie obejmuje scenariusz 3,4 Oto rozwiązanie pracy z ResponseEntity.class bezpośrednio:

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

Brzmi nieźle. Ale jak zostaną obsłużone scenariusze (3, 4)? –

+0

Dzięki za podpowiedź z 'ResponseEntity'!A co z ustawieniem właściwości 'supportedMediaTypes' dla' StringHttpMessageConverter' (patrz [moja odpowiedź] (http://stackoverflow.com/a/12979543/267197))? To też może być rozwiązanie. –

+2

Właśnie sprawdziłem twoje rozwiązanie za pomocą 'ResponseEntity': to nie działa. Typ zawartości jest nadpisywany przez konwerter wiadomości, a konwerter wiadomości jest wybierany (algorytmy przybliżone) przez przecięcie 'Accept-type' oraz converted' supportedMediaTypes'. –

8

Istnieje kilka aspektów odnoszących się do problemu:

  • StringHttpMessageConverter dodaje kota ch-all mime type */* do listy obsługiwanych typów nośników, natomiast MappingJackson2HttpMessageConverter jest powiązane tylko z application/json.
  • Gdy dostarcza produces = ..., ta wartość jest przechowywana w HttpServletRequest (patrz RequestMappingInfoHandlerMapping.handleMatch()) i po wywołaniu obsługi błędu ten typ MIME jest automatycznie dziedziczony i używany.

Rozwiązaniem w prostym przypadku byłoby umieścić StringHttpMessageConverter pierwszy na liście:

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

a także usunąć produces z @RequestMapping adnotacji:

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

Teraz:

  • StringHttpMessageConverter odrzuci wszystkie typy, które tylko MappingJackson2HttpMessageConverter może obsłużyć (MetaInformation, java.util.Collection, itp.), Umożliwiając ich przekazanie dalej.
  • W przypadku wyjątku w scenariuszu (5, 6) pierwszeństwo będzie mieć StringHttpMessageConverter.

Do tej pory tak dobrze, ale niestety rzeczy stają się bardziej skomplikowane z ObjectToStringHttpMessageConverter. W przypadku zwrotnego typu handler'a java.util.Collection<MetaInformation> ten konwerter wiadomości zgłosi, że może przekonwertować ten typ na java.lang.String. Ograniczenie wynika z faktu, że typy elementów kolekcji są wymazywane, a metoda AbstractHttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType) uzyskuje klasę java.util.Collection<?> do sprawdzenia, jednak w przypadku konwersji następuje niepowodzenie kroku konwersji ObjectToStringHttpMessageConverter. Aby rozwiązać ten problem mamy utrzymać produces dla @RequestMapping dopiskiem gdzie powinny być używane JSON konwerter, ale aby wymusić prawidłowy typ zawartości dla obsługi wyjątków, będziemy usuwać HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE atrybut z 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(); 
} 

Kontekst pozostaje takie samo jak to było pierwotnie :

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

teraz sytuacje (1,2,3,4) są obsługiwane prawidłowo ze względu na zawartość typu negocjacji oraz scenariusze (5,6) są przetwarzane obsługi wyjątków.

Alternatywnie można zastąpić zbierania typ zwracany z tablicami, a następnie rozwiązanie nr 1 ma zastosowanie znowu:

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

do dyskusji:

myślę że AbstractMessageConverterMethodProcessor.writeWithMessageConverters() nie powinny dziedziczyć klasę z wartości, ale raczej z podpisem metody:

Type returnValueType = returnType.getGenericParameterType();

i HttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType) powinno być zmienione na:

canWrite(Type returnType, MediaType mediaType)

lub (w przypadku, gdy jest zbyt ograniczające potencjalne przetworników klasy-based) do

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

Następnie parametryzowane typy mogą być obsługiwane prawidłowo i Rozwiązanie nr 1 będzie miało zastosowanie ponownie.

+0

Dziękujemy! To jest odpowiedź, która zadziałała dla mnie. W szczególności wiersz 'request.removeAttribute (HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);' lub rozszerzenie 'request.setAttribute (HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton (MediaType.APPLICATION_JSON));' –

Powiązane problemy