Spring WebMVCのformとFormatterの件

2014-01-10T00:00:00+00:00 Java Spring Framework

※おそらくは書いてる内容は自分以外は理解しにくいと思いますので興味ないならCTRL+Wしてください(笑)

Spring WebMVCをやってみる (18) – AnnotationFormatterFactory -でも書いてるけど

「getPrinter実装してもどういう作用で発生するのかが謎」。普通にFormatterを使う場合とかだと等で作用されるっていうはあるのだけど、このAnnotationFormatterFactoryの事象だけはgetPrinterメソッドが作用する事が無い

っていう件に関して。結論から言うと「やり方が悪い」っていうだけだった模様

いわゆる@ModelAttributeなオブジェクトにプッシュしてやる事で発生する模様。単純に

<form:form action="/swmvc/sample/save.action" method="post" modelAttribute="sample_form">

    <!-- java.util.Dateを@DateTimeFormatterでフォーマット出力をする -->
    <form:input path="createdAt" />

    <!-- sampleはenumでSampleFormatterを利用してフォーマット出力をする -->
    <form:input path="sample" />

    <input type="submit" value="save" />
</form:form>

で前回のだとURLから受け取ったデータをそのままModelMapに突っ込んでJSPで参照するっていう方式でやったけれども、それをやるといざform:input等で利用してもフォーマット出力されない

で検証してみた所だと@ModelAttributeで使用されるクラスでゲッターセッターを持ち、そのフィールドでアノテーションを付与しておいてそれを利用する事でgetPrinterを利用したフォーマット出力を利用できる模様

という事で長いけどやってみる。要件は上記でも書いてるようにEnumをフォーマット出力するAnnotationFormatterFactoryを利用する

Sample.java

package sample;

public enum Sample {

    HOGE("hoge");

    private String name;

    private Sample(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

SampleFormatter.java

package sample;

import java.text.ParseException;
import java.util.Locale;

import org.springframework.format.Formatter;

public class SampleFormatter implements Formatter<Sample> {

    @Override
    public String print(Sample object, Locale locale) {
        return object.name().toLowerCase();
    }

    @Override
    public Sample parse(String text, Locale locale) throws ParseException {
        return Sample.valueOf(text.toUpperCase());
    }
}

前回のだとURLのクエリーからこのparseメソッドを使ってStringからEnumへ変換するっていうのをやったけど、printが発生しない(正確にはAnnotationFormatterFactory#getPrinterが発生しない)

SampleFormatterFactory

package sample;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import org.springframework.format.AnnotationFormatterFactory;
import org.springframework.format.Parser;
import org.springframework.format.Printer;

public class SampleFormatterFactory implements AnnotationFormatterFactory<SampleFormat> {

    @Override
    public Set<Class<?>> getFieldTypes() {
        return new HashSet<Class<?>>(Arrays.asList(Sample.class));
    }

    @Override
    public Printer<?> getPrinter(SampleFormat annotation, Class<?> fieldType) {
        return new SampleFormatter();
    }

    @Override
    public Parser<?> getParser(SampleFormat annotation, Class<?> fieldType) {
        return new SampleFormatter();
    }
}

おそらくは前回のとまったく変わってないはずなので(ry

SampleForm.java

package sample;

import java.io.Serializable;
import java.util.Date;

import org.springframework.format.annotation.DateTimeFormat;

public class SampleForm implements Serializable {

    private static final long serialVersionUID = 1L;

    @SampleFormat
    private Sample sample;

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date createdAt = new Date();

    public Date getCreatedAt() {
        return createdAt;
    }

    public Sample getSample() {
        return sample;
    }

    public void setSample(Sample sample) {
        this.sample = sample;
    }
}

SampleController.java

package sample;

import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

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

    private static final String FORM_NAME = "sample_form";

    private SampleForm form;

    @InitBinder
    public void init(WebDataBinder binder) {
        // spring-web-servlet.xmlで設定してもいい
        ((DefaultFormattingConversionService)binder.getConversionService())
            .addFormatterForFieldAnnotation(new SampleFormatterFactory());
    }

    @ModelAttribute(FORM_NAME)
    public SampleForm setupForm() {
        form = new SampleForm();
        return form;
    }

    @RequestMapping("/index")
    public String index(@RequestParam("name") @SampleFormat Sample sample) {
        form.setSample(sample);

        return "index";
    }
}

っていう感じで@ModelAttributeなコマンドオブジェクトにリクエストからparseされたSampleなenumをセットする。でそのコマンドオブジェクトなSampleFormでSampleなenumは@SampleFormatがついてるので、それがAnnotationFormatterFactory#getPrinterからFormatter#printに処理が発生するっていう感じ

ちなみにSampleFormの@SampleFormatなアノテーションを外すとSampleFormatterでは処理されないがEnum#nameメソッドが表記で出力される模様

Androidでsocket.io mock-javamail