Spring MVCのJson変換機能で@DateTimeFormatが無視される件の対処方法
以前に、Spring MVCのJson変換機能について紹介しました。
Spring MVCでJSONデータを返すための手順 - 達人プログラマーを目指して
また、Springの型変換サービスについても以下で紹介しました。
SpringのJavaBeansアクセスAPIと型変換サービスは単独で利用しても利用価値が高いという事実 - 達人プログラマーを目指して
型変換では、@DateTimeFormatや@NumberFormatなどのアノテーションをBeanに付加することで、日付や数値のフォーマットが自動的に行われるようになっています。そうであれば、当然Json化する際にもこれらのアノテーションにより正しくフォーマットがされると期待してしまうのですが、現状Spring3.0.xでは
MappingJacksonHttpMessageConverter's ObjectMapper does not use ConversionService when binding JSON to JavaBean properties [SPR-6731] · Issue #11397 · spring-projects/spring-framework · GitHub
のバグがあり、Json化の際にこれらのアノテーションに従って正しくフォーマットしてくれません。Json化の日付フォーマットに関しては、Json化で内部的に利用しているJacksonというライブラリーの設定をすることで一応対処することもできます。
http://wiki.fasterxml.com/JacksonFAQDateHandling
http://wiki.fasterxml.com/JacksonFAQ#Custom_Serializers
しかし、せっかくSpring自身に型変換のしくみがあるのに、別々に設定しなくてはならないのはかなりいまいちです。この問題については、次のSpring3.1では修正される予定のようですが、とりあえず、暫定の対処方法を考えてみました。
まず、独自拡張された変換機能を動作させるために、以下の拡張クラスを作成します。*1
package com.github.ryoasai.springmvc.json; import javax.annotation.PostConstruct; import javax.inject.Inject; import org.codehaus.jackson.map.ObjectMapper; import org.springframework.core.convert.ConversionService; import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; public class ExMappingJacksonHttpMessageConverter extends MappingJacksonHttpMessageConverter { private ConversionService conversionService; public ConversionService getConversionService() { return conversionService; } @Inject public void setConversionService(ConversionService conversionService) { this.conversionService = conversionService; } @PostConstruct public void init() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setSerializerFactory( new ConversionServiceSerializerFactory(conversionService)); setObjectMapper(objectMapper); } }
このクラスではConversionServiceの変換機能を呼び出すため独自のConversionServiceSerializerFactoryをJacksonのObjectMapperに設定しています。なお、Jackson自体はオブジェクト指向設計の開放閉鎖原則の観点からは、かなり拡張が困難な設計となっていたため、ちょっと苦労したのですが、一応動作する例については以下を参照してください。
spring-mvc-exts/spring-mvc-exts at master · ryoasai/spring-mvc-exts · GitHub
あとは、以上のExMappingJacksonHttpMessageConverterをSpring MVCから使えるようにxmlの設定ファイルを以下のように記述します。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc"> <!-- Scans the classpath of this application for @Components to deploy as beans --> <context:component-scan base-package="com.github.ryoasai.springmvc.example" /> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.StringHttpMessageConverter" /> <bean class="org.springframework.http.converter.ResourceHttpMessageConverter" /> <bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter" /> <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter" /> <ref bean="mappingJacksonHttpMessageConverter" /> </list> </property> <property name="webBindingInitializer"> <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer"> <property name="conversionService" ref="conversionService" /> <property name="validator" ref="validator" /> </bean> </property> </bean> <!-- @Controller の設定をします。 --> <!-- Configures the @Controller programming model --> <mvc:annotation-driven conversion-service="conversionService"/> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" /> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> </bean> <bean id="mappingJacksonHttpMessageConverter" class="com.github.ryoasai.springmvc.json.ExMappingJacksonHttpMessageConverter" /> <!-- Configures the @Controller programming model --> <mvc:annotation-driven /> ... </beans>
これは独自の変換処理を追加するために、AnnotationMethodHandlerAdapterの宣言をしているのですが、単に独自のMessageConverterに差し替えるだけで
Make it easier to add new Message Converters to AnnotationMethodHandlerAdapter [SPR-7504] · Issue #12161 · spring-projects/spring-framework · GitHub
*1:まだ、試していませんが、Keith Donald氏の方法だとBeanPostProcessorを使って初期化しています。こちらのほうが賢かったかもしれません。https://src.springframework.org/svn/spring-samples/mvc-ajax/trunk/