12-2.return値の変換方法
(HttpMessageConverter)の技術的Tips

概要

前の記事でHttpMessageConverterの動作について記載しました。

ここでは、ケースごとに具体的なプログラミング方法を紹介したいと思います。

このTipsだけを見てしまうと正しい知識が把握できずに、勘違いが発生するかもしれませんので

必ず前の記事をお読みいただいたうえでご利用いただければと思います。

【補足】

このブログ全体に言えるのですが、Springの設定についてはXMLの形式で記述します。

というのは、最近はJavaConfigの記事が多くなりXMLの設定の記事が少なくなったことと、

XMLの記述から類推してJavaConfigは書けると思われるからです。

HttpMessageConverterの技術的Tips

ここでは、やりたいことのパターンごとにやり方の例をTips的に記述していこうと思います。

リクエストでJson送信、レスポンスでJson返却したい場合

以下のいづれかの方法で実現できるはずです。

・@RestControllerを指定

・URLの設計として拡張子を.html、.xml、.json以外指定して、引数に@RequestBodyを付け、自作PoJoをreturnする。

※@RequestBodyを使用する場合はリクエスト時にもConverterを決定し使用するが、決定時の条件にContent-typeも入ってくるので注意。

この記事では@ResponseBodyについて扱いたいため簡易な説明にしています。

上記のキーワードでネットを探すと色々と記事が出てくるかと思います。

レスポンスで文字列(HTMLやJson)をそのまま返したい場合(文字化けする場合の対処含む)

Controllerは以下のように記述します。

@RequestMapping(value="mem", method=RequestMethod.POST)

@ResponseBody

public String json(@RequestParam("id") int id){

return "<html><body>はーい!</body></html>";

}

上記の場合StringMessageConverterが採用されますが、前の記事のとおり、文字化けする危険があります。

デフォルトで設定されている文字コードはISO-XXという外国のコードだからです。

以下のように、デフォルト文字コードをutf-8で設定したConverterを追加してやればよいです。

【Spring設定ファイルの記述例】

<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">

<!-- Json文字列の文字化け対策 -->

<mvc:message-converters register-defaults="true">

<bean class="org.springframework.http.converter.StringHttpMessageConverter">

<property name="defaultCharset" value="utf-8"/>

</bean>

</mvc:message-converters>

</mvc:annotation-driven>

※上記の書き方だと、デフォルトで設定されているコンバーターの先頭に登録され、

元々のStringHttpMessageConverterも登録されたままになります(register-defaults="true"なので)。

何か悪さをするとは考えにくいですが、頭の片隅に置いておいていただけますと良いかと思います。

レスポンスでPoJoからJson文字列返却(URLが*.htmlではない場合)

web.xmlでSpringMVCを適用するURLの拡張子を.html以外にします。

【web.xmlの記述例】

<servlet-mapping>

<servlet-name>spring_mvc_sample</servlet-name>

<url-pattern>*.do</url-pattern>

</servlet-mapping>

レスポンスの型を自作PoJoにします。

【Controllerの記述例】

@RequestMapping(value="mem", method=RequestMethod.POST)

@ResponseBody

public MyMember json(int id){

return new MyMember(id);

}

<リクエスト例(jquery)>

$.post({

"url": "http://localhost:8080/xxx/mem.do",

"data" : {"id": 122},

"contentType": "application/x-www-form-urlencoded; charset=utf-8",

"method": "POST",

});

もし、リクエストのライブラリが予想外のAcceptヘッダを送ることでエラーになるようでしたら、

Acceptヘッダに"*/*"や"application/json"を指定するようにしましょう。

レスポンスでResponseEntityからJson文字列返却(文字化けしない方法含む(@ResponseBodyを使用しないパターン))

Controllerは以下のように記述します。

【Controllerの記述例】

@RequestMapping(value = "/json", method = RequestMethod.POST)

public ResponseEntity<String> bar() {

final HttpHeaders httpHeaders= new HttpHeaders();

httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);

return new ResponseEntity<String>("{\"test\": \"jsonResponseExample\"}", httpHeaders, HttpStatus.OK);

}

ResponseEntityをreturn値にする場合、Content-typeも自分で指定できますので

URLの拡張子もリクエストヘッダも気にせずにプログラムを書けます。

<リクエスト例(jquery)>

$.post({

"url": "http://localhost:8080/xxx/json.html",

"data" : params,

"contentType": "application/x-www-form-urlencoded; charset=utf-8",

"method": "POST",

});

jqueryはURLがあっていれば何でも大丈夫です。

レスポンスでPoJoからJson文字列返却(URLの拡張子を無視させる方法(URLが*.htmlの場合でも可能))

もし、web.xmlでSpringMvcで扱う拡張子を*.htmlなどにした場合に、コンバーターを使用したい場合(つまり@ResponseBodyを使用したい場合)は、

「拡張子の値によって、許されるMediaTypeを決定する機能」をOFFにするという方法があります。

【Spring設定ファイルの記述例】

<bean id="contentNegotiationManager"

class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">

<!-- URLの拡張子でMediaTypeを決める機能をOffにする -->

<property name="favorPathExtension" value="false" />

</bean>

<!-- Make this available across all of Spring MVC -->

<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />

※URLの拡張子がhtmlの場合、「レスポンスで許されるMediaType」を"text/html"にしてしまいます。前の記事を参照。

それを回避する方法が上記の方法です。

<リクエスト例(jquery)>

$.post({

"url": "http://localhost:8080/xxx/ttt.html",

"data" : params,

"contentType": "application/x-www-form-urlencoded; charset=utf-8",

"accept" : "application/json" ,

"method": "POST",

});

※これは、ここで紹介している他の方法とも組み合わせられます。

コンバーターの許されるMediaTypeを変更する方法

URLの拡張子が".html"でも、それ以外でも対応できる方法の紹介です。

MappingJackson2HttpMessageConverterでサポートするMediaTypeに、”text/html”, "application/x-www-form-urlencoded"を追加する方法です。

こうすると、URLの拡張子が.htmlの場合に「レスポンスで許されるMediaType」が"text/html"になっても

サポートされるようになります。

以下をSpringに設定します。

【Spring設定ファイルの記述例】

<mvc:annotation-driven>

<mvc:message-converters register-defaults="false">

<bean class="org.springframework.http.converter.StringHttpMessageConverter">

<property name="defaultCharset" value="utf-8"/>

</bean>

<!-- 最後にJackson2のコンバーターを追加します。先頭に追加すると他のコンバーターが採用されなくなる可能性があります。 -->

<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">

<property name="supportedMediaTypes" >

<list>

<value>application/x-www-form-urlencoded</value>

<value>application/json</value>

<value>text/plain</value>

<value>text/html</value>

<value>text/json</value>

</list>

</property>

</bean>

</mvc:message-converters>

</mvc:annotation-driven>


register-defaults="false"にして、デフォルトで登録されるコンバーターを登録させないところがコツです。

Jackson2のコンバーターは、サポートするクラス(型)が「ほぼどのクラスでも良い」となっています。

もしregister-defaults="true"にすると、上記のように記述したコンバーターが先頭に追加されますので、

Jackson2のコンバーターがすぐに採用されて、それ以降のコンバーターが採用されないからです。

※StringMessageConverterを設定していますがJsonしか扱わない場合は不要です。

登録の順番性をお見せしたいために、わざと記述しています。

サポートするMediaTypeの変更により、MappingJackson2HttpMessageConverterがこのリクエストを

サポートできるようになります。

【Controllerの記述例】

@RequestMapping(value="mem", method=RequestMethod.POST)

@ResponseBody

public MyMember json(@RequestParam("id") int id){

return new MyMember(id);

}


<リクエスト例(jquery)>

$.post({

"url": "http://localhost:8080/xxx/mem.html",

"data" : {"id": 12},

"contentType": "application/x-www-form-urlencoded; charset=utf-8",

"method": "POST",

});


MappingJackson2HttpMessageConverterを継承カスタマイズしてSpringに設定する方法

「レスポンスで許されるMediaType」を無視する方法として、上記でsupportedMediaTypesの設定変更を

する方法を紹介しました。

その他にも、クラス自体をカスタマイズすることでもっと自由に採用条件を変えることもできます。

canWrite()などを書き変えて、何かしら都合のようにカスタマイズします。

例えば、全てのMediaTypeをサポートするのではなく、返り値の型が、何かしらのマーカーインターフェースを

継承していたらMediaTypeに無関係にサポートするようにカスタマイズできます。

そうすれば、例えばマーカーインターフェースがThisIsJsonであれば、ThisIsJsonをJson変換する対象であることを示す、

インターフェースとして使用できると思います。

自作コンバーターを作成しましたら、上記の例のようにSpringに設定すれば使用されます。

Created Date: 2018/07/07 (七夕!)