33

リクエストを処理し、JSONにシリアル化されるBeanを返し、サービスで発生させることができるGET例外ハンドラーを提供するコントローラーがあるとします。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のmimeタイプ)によって処理されることを望んでいます。どうすればいいですか?

画像をより完全な(そして複雑な)ものにするために、次のハンドラーもあるとします。

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

このハンドラーを使用すると、戻り文字列をクライアントから渡されたものに応じて、両方MappingJackson2HttpMessageConverterでシリアル化できます。戻り値のタイプと値は次のようになります。StringHttpMessageConverterAccept-type

+ ---- + --------------------- + ---------------------- -+ ------------------ + ----------------------------- -------- +
| NN | URL | Accept-type | コンテンツタイプ| メッセージコンバーター|
| | | リクエストヘッダー| 応答ヘッダー| |
+ ---- + --------------------- + ---------------------- -+ ------------------ + ----------------------------- -------- +
| 1. | / version | text / html; * / * | テキスト/プレーン| StringHttpMessageConverter |
| 2. | / version | アプリケーション/json; * / * | アプリケーション/json| MappingJackson2HttpMessageConverter |
| 3. | / meta / 1 | text / html; * / * | アプリケーション/json| MappingJackson2HttpMessageConverter |
| 4. | / meta / 1 | アプリケーション/json; * / * | アプリケーション/json| MappingJackson2HttpMessageConverter |
| 5. | / meta / 0(例外)| text / html; * / * | テキスト/プレーン| StringHttpMessageConverter |
| 6. | / meta / 0(例外)| アプリケーション/json; * / * | テキスト/プレーン| StringHttpMessageConverter |
+ ---- + --------------------- + ---------------------- -+ ------------------ + ----------------------------- -------- +
4

2 に答える 2

30

produces = MediaType.APPLICATION_JSON_VALUEから@RequestMappingを削除するgetMetaInformationと、望ましい結果が得られると思います。

応答タイプは、Acceptヘッダーのcontent-type値に従ってネゴシエートされます。


編集

これはシナリオ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);
}
于 2012-10-19T16:53:57.193 に答える
15

問題に関連するいくつかの側面があります:

  • StringHttpMessageConverter*/*サポートされているメディアタイプのリストにcatch-allmimeタイプを追加します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処理できるため、さらに渡すことができます。MetaInformationjava.util.Collection
  • シナリオ(5、6)で例外が発生した場合はStringHttpMessageConverter、優先されます。

これまでのところ良いですが、残念ながら、物事はより複雑になりObjectToStringHttpMessageConverterます。ハンドラーの戻りタイプの場合java.util.Collection<MetaInformation>、このメッセージコンバーターはこのタイプをに変換できることを報告しjava.lang.Stringます。制限は、コレクション要素タイプが消去され、AbstractHttpMessageConverter.canWrite(Class<?> clazz, MediaType mediaType)メソッドがjava.util.Collection<?>チェック用のクラスを取得するという事実に起因しますが、変換ステップに関してObjectToStringHttpMessageConverterは失敗します。producesJSONコンバーターを使用する必要があるアノテーションに保持する問題を解決するために@RequestMapping、例外ハンドラーの正しいコンテンツタイプを強制するために、以下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 = "/meta", 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が再び適用可能になります。

于 2012-10-19T17:39:21.463 に答える