12-1.return値の変換方法
(HttpMessageConverter)について
概要
Spring MVCのリクエスト処理のメソッドの返り値は、様々な型を指定できます。
この返り値の型に応じて、最終的には、HTMLになったり、XMLになったり、画像データになったり、
変換されてブラウザに返っていきます。
さて、この変換処理ですが、「型ごとに条件分岐はしているが結局すべて同じ仕組みで変換されている」と思っていませんか?
実は違います!
実は変換の仕組みは2パターンあり、プログラム上で@ResponseBodyアノテーションを指定するかしないかでスイッチしています。
ここでは、@ResponseBodyを付けるパターンの仕組みを見ていきます。
もう一つの変換のパターンは最初に軽く触れますが、既に他の記事で紹介していますので詳しくはそちらをご覧いただければと思います。
【文字化けやJsonマッピングの問題等について】
おそらく、この記事に来られた方は、SpringMVCでの文字化けや、Jsonのリクエストのマッピングに問題があり
悩まれてこられていると思います。
具体的な文字化け対策や、その他の状況に応じた設定などは別の記事に書こうと思います。
でも、本記事を読まないと分からないと思いますので、まずは本記事をお読みいただければと思います。
はじめに~間違った情報の氾濫(文字化け等の対策について)
インターネットの記事を読んでいると、文字化けに遭遇した場合は「@RequestMappingのproducersで
文字コードを指定すると、レスポンスの文字コードを指定できる」など、間違った情報が氾濫しています。
それぞれの役割や意味を正確に把握していれば、文字コードの問題などもすぐに解決できます。
ですので、正確に情報を把握することで、みんなが悩みから解決されることを目指して記事を起こしました。
まず把握しておくこと
まずは、@RequestMappingの役割を把握しましょう!
【@RequestMappingの役割】
@RequestMappingは、リクエストに対して、それを処理するメソッドを決める役割しかありません。
アノテーションの名前がまさに名が体を表していますね。
ちなみに、このアノテーションで指定できる主な属性を確認しておきましょう。
<@RequestMappingで指定できる主なプロパティ>
※詳しい記述方法などは他の記事に記載していますのでそちらを参照
リクエストの情報に対して、指定したすべてにマッチしたメソッドが呼び出されます。
上記のとおり、
@RequestMappingは、単に、使用するメソッドを絞っているだけで、
レスポンスのヘッダ情報の設定にも、メソッドの返り値の変換方法(例えば文字コード指定)にも、
一切関与しません。
では、何がレスポンスのContent-typeや、返り値の変換方法を決めているのでしょうか?
レスポンスのContent-typeや返り値の変換方法を決めているもの
Content-typeや返り値の変換方法を決めているものは以下の2つのパターンがあります。
あとで詳しく見ていきます。
@ResponseBodyを使用しないパターン
@RequestMapping(value="ref", method=RequestMethod.GET)
public String reference() {
return "user-Edit-Input";
}
上記のような場合、返り値の型のみで、Content-typeと返り値の変換方法が決まります。
返り値の型(上記だとString)に応じて、Springが適切にContent-typeや返り値の変換を処理してくれます。
ただし、返り値の型として指定できるものは決まっています。こちらを参考。
@ResponseBodyを使用するパターン
@RequestMapping(value="/ref", method=RequestMethod.GET)
@ResponseBody
public MyMember reference() {
return new MyMember(1);
}
上記のような場合、以下の手順でレスポンスのContent-typeと返り値の変換方法が決まります。
①リクエストのヘッダ情報と返り値の型からHttpMessageConverter(コンバーター)が決まる
②コンバーターがContent-typeの設定と、返り値の変換を処理する。
コンバーターはSpringに複数設定されていて、ヘッダ情報と返り値の型から、どのコンバーターを使用するかを決めます。
コンバーターが何なのか?、どうやって使用するコンバーターを決定するかは以下で見ていきます。
HttpMessageConverterの動作
【コンバーター(Converter)とは】
コンバーターとは、リクエストやレスポンスで、引数や返り値に対して特殊な変換をしたい場合に使用する「変換器」です。
具体的には、変換器はHttpMessageConverterの派生クラスになります。
これらのクラスは、フィルタのようにデフォルトで複数登録されていますが、後述の決定条件によって1つが採用され、
採用されたコンバーターがリクエスト処理の返り値の処理をします。
例えば、PoJoをXML文字列やJson文字列に変換したり、逆変換もできます(下のHttpMessageConverter一覧参照)。
コンバーターは@RequestBodyを使用することでリクエスト処理の引数に対しても使用することができますが、
この記事では、基本的にレスポンス時に行われる処理だけを説明します。
【使用するコンバーターの決定方法を簡単にいうと?】
まずはコンバーター決定方法の要点だけを簡単に書いてみます。
<決定条件の概要>
各コンバーターには自身が処理できるMediaTypeが決まっており、
クライアント(ブラウザ)が要求するMediaTypeを処理できる場合に、そのコンバーターが採用されます。
最初に見つかったコンバーターが、Content-typeを決め、returnの変換をします。
※「処理できるMediaType」はSpring上のドキュメントでは「サポートするMediaType」という書き方をしています。
コンバーターは、メソッドのreturnの型のみで決まるわけではないことにご注意ください。
【コンバーターの決定条件(どのConverterを採用するか?)】
ここでは決定条件方法を詳細に見ていきます。
上記で記述した決定の概要を実際のSpringプログラムの流れに沿ってみていきます。
上記の決定概要と違っているように見えてしまうかもしれませんが、よく読むと結局同じことを言っている事が分かると思います。
<具体的なコンバーター決定方法の詳細>
具体的なコンバーターの決定方法は、以下のようになります。
①クライアントが要求するMediaType(レスポンスで許されるメディアタイプ)を抽出する。
1.URLの拡張子がxml, html, jsonの場合(例:/test/a.html の場合はhtml)
それぞれメディアタイプは、"text/html", "text/xml", "text/json"になります。
2.上記以外の場合
メディアタイプは、HTTPヘッダAcceptのメディアタイプにします。
(通常クライアントはAccept:"*/*"を投げるので何でもOKで、Coverterの採用条件に寄与しないはず)
②返り値(return)の型をサポートしている(処理できる)コンバーターを全て探す。
コンバーター自身がサポートできる型を知っているので、登録順にコンバーターに聞いていきます。
③上記の②で抽出されたコンバーターに、①のメディアタイプをサポートしているか確認する。
コンバーターは自身がサポートできるメディアタイプを知っているので、順番にサポートできるかを聞いていき
最初にOKを返したコンバータを採用します。
例えば、①が"text/html"、②で抽出されたコンバーターのうち、コンバーター1が"text/*"をサポートしている場合、
マッチするのでコンバーター1が採用となります。
④決定したコンバーターで返り値を変換し、Content-typeを設定してクライアントに返却する。
<上記を簡単に箇条書きすると>
コンバーターは以下の3つの条件で決まる。
・リクエストのURLの拡張子(「.xml、.html、.json」のいずれか、もしくはそれ以外か)
・リクエストヘッダのAccept
・処理メソッドのreturnの型
URLの拡張子に.html、.xml、.jsonを使用している場合は注意!
【使用するコンバーターの決定方法をイメージ図で描くと?】
「リクエスト」の情報と「処理メソッド」のクラス(型)から、HttpMessageConverterを決定する様子を描いてみました。
詳しくは下図と上記の動作詳細をお読みください。
以下の例では、それぞれのURLにJsonをPOSTで投げた場合をイメージしています。
<コンバーターの検索に成功した例>
ポイントは、URLの拡張子が".do"なので、レスポンスで許されるMediaTypeがリクエスト情報の
Acceptヘッダになっている所です(コンバーターの決定条件①参照)。
今回はAcceptヘッダは"application/json"ですので、許されるMediaTypeも"application/json*"になり、
最後のJacksonのコンバーターが、MediaType、returnの型ともにマッチして採用されています。
<コンバーターの検索に失敗した例>
ポイントは、この例では、URLの拡張子が".html"のため、
レスポンスで許されるMediaTypeが"text/html"になっている所です(コンバーターの決定条件①参照)
このため最後の、Jackson2のコンバーターすら採用できず、エラーになってしまいます。
例えばですが、Jackson2のコンバーターの「サポートするMediaType」が
"text/html"や"text/*"だった場合は、上記の例でも採用されていました。
このように、@ResponseBodyを使用した場合は、リクエストのヘッダ情報と処理メソッドの返り値の型から
動作が決まります。
参考:Converterの種類とサポートするMediaType、返り値の型
HttpMessageConverterはフィルタのように複数設定されています(上図を参照)。
上図のように、コンバーターの「サポートするMediaType」と「サポートするクラス(型)」が分かっていれば
どのコンバーターが適用されるかが分かります。
参考までに、デフォルトで設定されるConverterを一覧で見ていきます。
HttpMessageConverterの一覧(登録順)
※@RequestBodyの場合は逆変換します
Javadocを元に一覧を作りましたが分からなかったところは「?」にしています。
文字化けする問題について考えてみましょう
良く話題になる文字化けの問題に触れてみようと思います。
まず、文字化けの問題に遭遇される方の多くは以下のように
@ResponseBodyを指定して、StringでJsonなどを返す形式にしていることが多いのではないでしょうか?
@RequestMapping(value="json", method=RequestMethod.POST)
@ResponseBody
public String json(@RequestParam("id") int id){
return "{'id': 123}";
}
さて、通常、コンバーターは上の一覧のような順番でSpringに登録されているはずです。
メソッドの返り値のクラスがStringですので、StringMessageConverterが採用されます。
どのコンバーターが採用されるかは、返り値のクラスだけでなく、URLの拡張子とリクエストのAcceptヘッダも関係しますが、
StringMessageConverterがサポートするMediaTypeが"*/*"なのでAcceptヘッダなどは寄与しません。
しかし、一覧の説明のとおり、StringMessageConverterはデフォルトではISO-8859-1に文字コード変換しますので
文字化けします。
回避策は、ここまで分かれば予想がつくと思いますが、
StringMessageConverterの文字コードの設定を"utf-8"などに変更すればよいです。
Springで扱うクラスは必ずと言っていいほど設定変更の方法が用意されているので、一度設定変更するSetterがないかを
Javadocで調べることは有益なことです。
具体的なやり方は次の技術的Tipsの記事に書きました。
参考:動作変更など(文字コード指定など)で関わる主なクラスの紹介
コンバーター関連でよく使用するクラスなどを紹介します。
具体的な使い方などの技術Tipsのようなものは別記事に書く予定です。
・MappingJackson2HttpMessageConverter
Jsonを受信したり、レスポンスしたりのときに使用される。
・StringHttpMessageConverter
Stringをそのまま返したい場合に、文字コードをこのコンバーターが決める。
・ContentNegotiationManagerFactoryBean
ContentNegotiationManagerは、コンバーターの採用条件に関わるクラスです。
例えば、URLの拡張子やAcceptヘッダからレスポンスで許されるMediaTypeを決めています。
採用条件を変えたい場合は活躍します。
Created Date: 2018/07/07