Spring WebMVCをやってみる (5) - PathVariable -

2013-12-09T00:00:00+00:00 Java Spring Framework

例えばRequestMappingで指定するパスの一部を変数で引数にマッピングしたい時とかに使うのが@PathVariableアノテーションな模様。

SampleController.java

のようなURLを使い、日付な部分を@RequestMappingで使用するメソッドの引数に値をバインドするっていうようなケースな場合

package sample;

import java.util.Date;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/sample")
public class SampleController {
    @RequestMapping("/test/{date}")
    @ResponseBody
    public String test(@PathVariable Date date) throws Exception {
        return "date: " + date;
    }
}

っていうように引数なアノテーションに@PathVariableを指定する。但し、Date型は無設定な状態だと型変換されないので

Failed to convert value of type 'java.lang.String' to required type 'java.util.Date'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [java.util.Date]: no matching editors or conversion strategy found」

っていうようなエラーが起きる。なので@InitBinderを使ってDate型を処理するPropertyEditorを設定すれば良い模様

package sample;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/sample")
public class SampleController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);

        binder.registerCustomEditor(
            Date.class,
            new CustomDateEditor(dateFormat, false)
        );
    }

    @RequestMapping("/test/{date}")
    @ResponseBody
    public String test(@PathVariable Date date) throws Exception {
        return "date: " + date;
    }
}

ちなみにこの@InitBinderはWebBindingInitializerインターフェースを実装する事でコントローラーで@InitBinderを利用しなくても出来る

package sample;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.WebRequest;

public class DateTimeBindingInitializer implements WebBindingInitializer {

    @Override
    public void initBinder(WebDataBinder binder, WebRequest request) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        format.setLenient(false);

        binder.registerCustomEditor(
            Date.class,
            new CustomDateEditor(
                format,
                false // isEmptyAllowed
            )
        );
    }
}

っていうクラスを作っておいて、spring-web-servlet.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:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        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">

    <context:component-scan base-package="sample" />

    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="webBindingInitializer">
            <bean class="sample.DateTimeBindingInitializer" />
        </property>
    </bean>
</beans>

っていうような感じで作ったWebBingingInitializerな実装を注入させる事が出来ると

あとは普通にテストケースなりで

package swmvc;

import org.junit.Test;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

public class SampleControllerTest extends AbstractTestCase {
    @Test
    public void test() throws Exception {
        mock.perform(get("/sample/test/2013-01-01"))
            .andExpect(status().isOk());
    }
}

余談: @DateTimeFormatを使う

@PathVariableに@DateTimeFormatを設定してもDate型をサポートさせる事が出来る模様

package sample;

import java.util.Date;

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import static org.springframework.format.annotation.DateTimeFormat.ISO.DATE;

@Controller
@RequestMapping("/sample")
public class SampleController {
    @RequestMapping("/test/{date}")
    @ResponseBody
    public String test(@PathVariable @DateTimeFormat(iso = DATE) Date date) throws Exception {
        return "date: " + date;
    }
}

但しこれを使うにはconversationServiceの設定が必要な模様

<?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:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        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">

    <context:component-scan base-package="sample" />

    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="webBindingInitializer">
            <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
                <property name="conversionService">
                    <bean class="org.springframework.format.support.FormattingConversionServiceFactoryBean" />
                </property>
            </bean>
        </property>
    </bean>
</beans>

Spring WebMVCをやってみる (6) - @RequestParam - Spring WebMVCをやってみる (4) - AbstractViewを利用した独自Viewを利用する -