Bean Validationとの連携

Bean Validationによるカラムの値の検証を行う方法を説明します。

ライブラリの追加

Bean Validationを利用する際には、ライブリを追加します。 Mavenを利用している場合は、pom.xmlに以下を追加します。

本ライブラリは、Bean Validation1.0/1.1の両方に対応しており、 その参照実装である Hibernate Validator を追加します。

Bean Validation 1.1(JSR-349)を利用する場合は、Hibernate Validator5.x系を利用します。 さらに、メッセージ中にJava EEのEL式が利用可能となっているため、その実装であるライブリを追加します。

  • ただし、TomcatやGlassFishなどのWebコンテナ上で使用するのであれば、EL式のライブラリはそれらに組み込まれているため必要ありません。

  • また、本ライブラリの機能を利用して、JEXL に切り替えるのであれば、式言語の追加は必要ありません。

pom.xmlの依存関係の追加(Bean Validation1.1を利用する場合)
 1<!-- Bean Validation 1.1 -->
 2<dependency>
 3    <groupId>javax.validation</groupId>
 4    <artifactId>validation-api</artifactId>
 5    <version>1.1.0.Final</version>
 6</dependency>
 7<dependency>
 8<groupId>org.hibernate</groupId>
 9    <artifactId>hibernate-validator</artifactId>
10    <version>5.3.3.Final</version>
11</dependency>
12
13<!-- EL式のライブラリが必要であれば追加します -->
14<dependency>
15    <groupId>org.glassfish</groupId>
16    <artifactId>javax.el</artifactId>
17    <version>3.0.1-b08</version>
18</dependency>

Bean Validation 1.0(JSR-303)を利用する場合は、Hibernate Validator4.x系を利用します。 Bean Validation 1.0では、メッセージ中でEL式は利用できませんが、本ライブラリの機能を使用すれば、JEXL が利用できます。

pom.xmlの依存関係の追加(Bean Validation1.0を利用する場合)
 1<!-- Bean Validation 1.0 -->
 2<dependency>
 3<groupId>javax.validation</groupId>
 4    <artifactId>validation-api</artifactId>
 5    <version>1.0.0.GA</version>
 6</dependency>
 7<dependency>
 8    <groupId>org.hibernate</groupId>
 9    <artifactId>hibernate-validator</artifactId>
10    <version>4.3.2.Final</version>
11</dependency>

Bean Validationの利用方法

アノテーション @CsvBean(validatosr=CsvBeanValidator.class) を指定します。

CsvBeanValidator は、Bean Validation と、本ライブラリの CsvValidator をブリッジするクラスです。

独自のメッセージソースは、クラスパスのルートに HibernateValidation.properties を配置しておけば自動的に読み込まれます。

 1import com.github.mygreen.supercsv.annotation.CsvBean;
 2import com.github.mygreen.supercsv.annotation.CsvColumn;
 3import com.github.mygreen.supercsv.validation.beanvalidation.CsvBeanValidator;
 4
 5import javax.validation.constraints.AssertTrue;
 6import javax.validation.constraints.DecimalMax;
 7import javax.validation.constraints.Pattern;
 8
 9import org.hibernate.validator.constraints.Length;
10import org.hibernate.validator.constraints.NotEmpty;
11import org.hibernate.validator.constraints.Range;
12
13// Bean Validationの指定方法
14@CsvBean(validators=CsvBeanValidator.class)
15private static class TestCsv {
16
17    @CsvColumn(number=1)
18    @NotEmpty
19    private String id;
20
21    @CsvColumn(number=2)
22    @Length(max=10)
23    @Pattern(regexp="[\\p{Alnum}]+", message="半角英数字で設定してください。")
24    private String name;
25
26    @CsvColumn(number=3)
27    @Range(min=0, max=100)
28    private Integer age;
29
30    @CsvColumn(number=4)
31    boolean used;
32
33    // 相関チェック
34    @AssertTrue(message="{name}が設定されている際には、{age}は必須です。")
35    boolean isValidAgeRequired() {
36        if(name != null && !name.isEmpty()) {
37            return age != null;
38        }
39
40        return false;
41    }
42
43    // setter/gettterは省略
44}

Bean Validation による値の検証でエラーがある場合は、例外 SuperCsvBindingException としてスローされます。

  • 例外クラスは、 CsvExceptionConverter メッセージに変換します。

  • CsvExceptionConverter は、CsvAnnotationBeanReader/CsvAnnotationBeanWriter に組み込まれているため、 メソッド #getErrorMessages() で取得できます。

 1import java.nio.charset.Charset;
 2import java.nio.file.Files;
 3import java.io.File;
 4import java.util.ArrayList;
 5import java.util.List;
 6
 7import org.supercsv.prefs.CsvPreference;
 8import org.supercsv.exception.SuperCsvException;
 9
10import com.github.mygreen.supercsv.io.CsvAnnotationBeanReader;
11import com.github.mygreen.supercsv.validation.CsvExceptionConverter;
12
13
14public class Sample {
15
16    public void sampleRead() {
17
18        CsvAnnotationBeanReader<SampleCsv> csvReader;
19        try {
20            csvReader = new CsvAnnotationBeanReader<>(
21                SampleCsv.class,
22                Files.newBufferedReader(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
23                CsvPreference.STANDARD_PREFERENCE);
24
25            // ファイルの読み込み
26            List<SampleCsv> list = csvReader.readAll();
27
28        } catch(SuperCsvException e) {
29
30            // 変換されたエラーメッセージの取得
31            List<String> messages = csvReader.getErrorMessages();
32
33        } finally {
34            if(csvReader != null) {
35                csvReader.close();
36            }
37        }
38    }
39
40}

Bean Validationのカスタマイズ

本ライブラリ用のメッセージソースや JEXL に切り替える場合、 Bean Validationのインスタンスを変更する必要があります。

その場合は、@CsvBean(validators=CsvBeanValidator.class) で指定するのではなく、 メソッド CsvAnnotationBeanReader#addValidators(...)CsvAnnotationBeanWriter#addValidators(...) で直接追加します。

 1import com.github.mygreen.supercsv.builder.BeanMapping;
 2import com.github.mygreen.supercsv.builder.BeanMappingFactory;
 3import com.github.mygreen.supercsv.localization.MessageInterpolator;
 4import com.github.mygreen.supercsv.localization.MessageResolver;
 5import com.github.mygreen.supercsv.localization.ResourceBundleMessageResolver;
 6
 7import java.nio.charset.Charset;
 8import java.nio.file.Files;
 9import java.io.File;
10
11import org.supercsv.prefs.CsvPreference;
12
13public class Sample {
14
15    // Beanの定義(validatorsの指定は行わない)
16    @CsvBean
17    public static class SampleCsv {
18        // 省略
19    }
20
21    public void sampleBeanValidationCustom() {
22
23        // CsvReaderの作成
24        CsvAnnotationBeanReader<SampleCsv> csvReader = new CsvAnnotationBeanReader<>(
25                SampleCsv.class,
26                Files.newBufferedReader(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
27                CsvPreference.STANDARD_PREFERENCE);
28
29        // BeanValidator用のValidatorの作成
30        final ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
31
32        // メッセージ処理クラスを本ライブラリのものに入れ替えてインスタンスを生成する
33        final Validator beanValidator = validatorFactory.usingContext()
34            .messageInterpolator(new MessageInterpolatorAdapter(
35                new ResourceBundleMessageResolver(),
36                new MessageInterpolator()))
37            .getValidator();
38
39        // Validatorの追加
40        csvReader.addValidators(beanValidator);
41
42    }
43
44}

メッセージ中の変数として、既存の変数に加えて、CSV用の次の変数が登録されており利用可能です。

メッセージ中で利用可能な変数

変数名

説明

lineNumber

CSVの実ファイル上の行番号。
カラムの値に改行が含まれている場合を考慮した実際の行番号なります。
1から始まります。

rowNumber

CSVの論理上の行番号です。
1から始まります。

columnNumber

CSVの列番号です。
1から始まります。

label

@CsvColumn(label="<見出し>") 出指定したカラムの見出し名です。
label属性を指定していない場合は、フィールド名になります。

validatedValue

不正となった値です。

printer

各クラスの TextFormatter のインスタンスです。
${printer.print(validatedValue)} で、オブジェクトをフォーマットするのに利用します。

BeanValidationとSpring Frameworkとの連携

Spring Frameworkと連携することで、コードがシンプルになります。 また、独自のフィールド用のValidator内にSpringBeanをインジェクションすることも可能です。

XMLによるコンテナの設定

XMLによる設定方法を説明します。

コンテナの定義の基本は次のようになります。

  • アノテーションによるDIの有効化を行います。

  • コンポーネントスキャン対象のパッケージの指定を行います。

  • com.github.mygreen.supercsv.builder.SpringBeanFactory をSpringBeanとして登録します。

  • Springの MessageSource で、本ライブラリのエラーメッセージ com.github.mygreen.supercsv.localization.SuperCsvMessages を読み込んでおきます。 * 独自のエラーメッセージがあれば、追加で定義します。

    • com.github.mygreen.supercsv.localization.SpringMessageResolver に、MessageSource を渡します。

  • CsvBeanValidator に、Springの LocalValidatorFactoryBean で作成したBeanValidationのValidtorのインスタンスを渡します。

 1<?xml version="1.0" encoding="UTF-8"?>
 2<!-- XMLによるコンテナの定義 -->
 3<beans xmlns="http://www.springframework.org/schema/beans"
 4    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5    xmlns:context="http://www.springframework.org/schema/context"
 6    xsi:schemaLocation="http://www.springframework.org/schema/beans
 7        http://www.springframework.org/schema/beans/spring-beans.xsd
 8        http://www.springframework.org/schema/context
 9        http://www.springframework.org/schema/context/spring-context.xsd
10    ">
11
12    <!-- アノテーションによるDIの有効化の定義 -->
13    <context:annotation-config />
14
15    <!-- コンポーネントスキャン対象のパッケージの指定 -->
16    <context:component-scan base-package="sample.spring" />
17
18    <!-- Springのコンテナを経由するCSV用のBeanFactoryの定義 -->
19    <bean id="springBeanFacatory" class="com.github.mygreen.supercsv.builder.SpringBeanFactory" />
20
21    <!-- Spring標準のメッセージソースの定義 -->
22    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
23        <property name="basenames">
24            <list>
25                <value>com.github.mygreen.supercsv.localization.SuperCsvMessages</value>
26                <value>TestMessages</value>
27            </list>
28        </property>
29    </bean>
30
31    <!-- Super CSV Annotation 用のMessgeResolverの定義 -->
32    <bean id="springMessageResolver" class="com.github.mygreen.supercsv.localization.SpringMessageResolver">
33        <property name="messageSource" ref="messageSource" />
34    </bean>
35
36    <!-- BeanValidation用のCsvValidatorの定義 -->
37    <bean id="csvBeanValidator" class="com.github.mygreen.supercsv.validation.beanvalidation.CsvBeanValidator">
38        <constructor-arg>
39            <bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
40                <property name="messageInterpolator">
41                    <bean class="com.github.mygreen.supercsv.validation.beanvalidation.MessageInterpolatorAdapter">
42                        <constructor-arg ref="springMessageResolver" />
43                        <constructor-arg><bean class="com.github.mygreen.supercsv.localization.MessageInterpolator" /></constructor-arg>
44                    </bean>
45                </property>
46            </bean>
47        </constructor-arg>
48    </bean>
49
50</beans>

JavaConfigによるコンテナの設定

Spring Framework3.0から追加された、JavaソースによるSpringBean定義の方法を説明します。

JavaConfigによる設定を使用する場合は、Spring Frameworkのバージョンをできるだけ最新のものを使用してください。 特に、機能が豊富なバージョン4.0以上の使用を推奨します。

 1import javax.validation.Validator;
 2
 3import org.springframework.context.MessageSource;
 4import org.springframework.context.annotation.Bean;
 5import org.springframework.context.annotation.ComponentScan;
 6import org.springframework.context.annotation.Configuration;
 7import org.springframework.context.annotation.Description;
 8import org.springframework.context.support.ResourceBundleMessageSource;
 9import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
10
11import com.github.mygreen.supercsv.builder.SpringBeanFactory;
12import com.github.mygreen.supercsv.localization.MessageInterpolator;
13import com.github.mygreen.supercsv.localization.SpringMessageResolver;
14import com.github.mygreen.supercsv.validation.beanvalidation.CsvBeanValidator;
15import com.github.mygreen.supercsv.validation.beanvalidation.MessageInterpolatorAdapter;
16
17
18// Javaによるコンテナの定義
19@Configuration
20@ComponentScan(basePackages="sample.spring")
21public class SuperCsvConfig {
22
23    @Bean
24    @Description("Springのコンテナを経由するCSV用のBeanFactoryの定義")
25    public SpringBeanFactory springBeanFactory() {
26        return new SpringBeanFactory();
27    }
28
29    @Bean
30    @Description("Spring標準のメッセージソースの定義")
31    public MessageSource messageSource() {
32        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
33        messageSource.addBasenames("com.github.mygreen.supercsv.localization.SuperCsvMessages", "TestMessages");
34        return messageSource;
35    }
36
37    @Bean
38    @Description("本ライブラリのSpring用のMessgeResolverの定義")
39    public SpringMessageResolver springMessageResolver() {
40        return new SpringMessageResolver(messageSource());
41    }
42
43    @Bean
44    @Description("Spring用のBeanValidatorのValidatorの定義")
45    public Validator csvLocalValidatorFactoryBean() {
46
47        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
48
49        // メッセージなどをカスタマイズ
50        validator.setMessageInterpolator(new MessageInterpolatorAdapter(
51                springMessageResolver(), new MessageInterpolator()));
52        return validator;
53    }
54
55    @Bean
56    @Description("CSV用のCsvValidaotrの定義")
57    public CsvBeanValidator csvBeanValidator() {
58
59        // ValidarorのインスタンスをSpring経由で作成したものを利用する
60        CsvBeanValidator csvBeanValidator = new CsvBeanValidator(csvLocalValidatorFactoryBean());
61        return csvBeanValidator;
62    }
63
64}

独自のConstraintValidatorの作成

Bean Validationの独自のアノテーションを作成する際には、通常の方法と同じです。

  • メタアノテーション @Constraint を付与します。

    • 属性 validatedBy に、 ConsraintValidator の実装クラスを指定します。

  • 複数指定可能できるように、内部クラス List を定義しておきます。

    • Bean Validation 1.1の段階では、Java8から追加された @Repeatable は対応していませんが、 従来の定義方法と揃えておくことで、@Repeatable を使ってJava8のスタイルで使用することができます。

    • ただし、今後リリース予定のBeanValidator2.0から @Repeatable 対応するため、定義しておいても問題はありません。

 1import java.lang.annotation.Documented;
 2import java.lang.annotation.ElementType;
 3import java.lang.annotation.Repeatable;
 4import java.lang.annotation.Retention;
 5import java.lang.annotation.RetentionPolicy;
 6import java.lang.annotation.Target;
 7
 8import javax.validation.Constraint;
 9import javax.validation.Payload;
10
11//BeanValidationの制約のアノテーション
12@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
13@Retention(RetentionPolicy.RUNTIME)
14@Documented
15@Repeatable(UserMailPattern.List.class) // 対応していないので、定義しなくても良い。
16@Constraint(validatedBy=UserMailPatternValidator.class)
17public @interface UserMailPattern {
18
19    // 共通の属性の定義
20    Class<?>[] groups() default {};
21    String message() default "{sample.spring.UserMailPattern.message}";
22    Class<? extends Payload>[] payload() default {};
23
24    // 複数のアノテーションを指定する場合の定義
25    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
26    @Retention(RetentionPolicy.RUNTIME)
27    @Documented
28    @interface List {
29        UserMailPattern[] value();
30    }
31
32}

ConstraintValidator の実装中で、SpringBeanのインジェクションを行いたい場合は、アノテーション @Resource/@Autowired など使います。

ConstraintValidator 自身は、SpringBeanとして登録する必要はありません。

 1import javax.validation.ConstraintValidator;
 2import javax.validation.ConstraintValidatorContext;
 3
 4import org.springframework.beans.factory.annotation.Autowired;
 5
 6
 7// ConstraintValidatorの実装
 8public class UserMailPatternValidator implements ConstraintValidator<UserMailPattern, String> {
 9
10    // SpringBeanをインジェクションします。
11    @Autowired
12    private UserService userService;
13
14    @Override
15    public void initialize(final UserMailPattern constraintAnnotation) {
16
17    }
18
19    @Override
20    public boolean isValid(final String value, final ConstraintValidatorContext context) {
21
22        // nullの場合は対象外
23        if(value == null) {
24            return true;
25        }
26
27        return userService.isMailPattern(value);
28    }
29
30}

CsvBeanの定義

CSVのBeanの定義では、@CsvBean(validator=CsvBeanValidator.class) で、CsvBeanValidatorを指定します。

 1import com.github.mygreen.supercsv.annotation.CsvBean;
 2import com.github.mygreen.supercsv.annotation.CsvColumn;
 3import com.github.mygreen.supercsv.validation.beanvalidation.CsvBeanValidator;
 4
 5
 6@CsvBean(header=true, validator=CsvBeanValidator.class)
 7public class UserCsv {
 8
 9    @CsvColumn(number=1, label="メールアドレス")
10    @UserMailPattern   // 独自のBeanValidator用のアノテーションの指定
11    private String mail;
12
13    // setter/getterは省略
14
15}

値の検証方法

  • BeanMappingFactory#getConfiguration() 取得できる、システム設定に、SpringBeanFactoryを設定します。

  • CsvExceptionConverter#setMessageResolver(...) に、SpringBeanとして定義した SpringMessageResolver を設定します。

    • さらに、 CsvAnnotationBeanReader#setExceptionConverter(...) に、作成した CsvExceptionConverter を渡します。

 1import com.github.mygreen.supercsv.builder.BeanMapping;
 2import com.github.mygreen.supercsv.builder.BeanMappingFactory;
 3import com.github.mygreen.supercsv.io.CsvAnnotationBeanReader;
 4
 5import java.nio.charset.Charset;
 6import java.nio.file.Files;
 7import java.io.File;
 8import java.util.ArrayList;
 9import java.util.List;
10
11import org.supercsv.prefs.CsvPreference;
12import org.supercsv.exception.SuperCsvException;
13
14@Service
15public class CsvService {
16
17    @Autowired
18    private SpringBeanFactory beanFactory;
19
20    @Autowired
21    private SpringMessageResolver messageResolver;
22
23    public void sampleSpring() {
24
25        // BeanMappingの作成 - SpringBeanFactoryを設定する
26        BeanMappingFactory beanMappingFactory = new BeanMappingFactory();
27        beanMappingFactory.getConfiguration().setBeanFactory(beanFactory);
28
29        // BeanMappingの作成
30        BeanMapping<UserCsv> beanMapping = mappingFactory.create(UserCsv.class);
31
32        CsvAnnotationBeanReader<UserCsv> csvReader;
33        try {
34            csvReader = new CsvAnnotationBeanReader<>(
35                    beanMapping,
36                    Files.newBufferedReader(new File("user.csv").toPath(), Charset.forName("Windows-31j")),
37                    CsvPreference.STANDARD_PREFERENCE);
38
39            // CsvExceptionConverterの作成 - SpringMessageResolverを設定する
40            CsvExceptionConverter exceptionConverter = new CsvExceptionConverter();
41            exceptionConverter.setMessageResolver(messageResolver);
42
43            // CsvExceptionConverterを設定する
44            svReader.setExceptionConverter(exceptionConverter);
45
46            // ファイルの読み込み
47            List<SampleCsv> list = csvReader.readAll();
48
49        } catch(SuperCsvException e) {
50
51            // 変換されたエラーメッセージの取得
52            List<String> messages = csvReader.getErrorMessages();
53
54        } finally {
55            if(csvReader != null) {
56                csvReader.close();
57            }
58        }
59    }
60
61}