Skip to content

独自バリデーション向けアノテーションを作成

バリデーションに使うアノテーションを自作します。

事前準備

下記依存関係を追加します:

        <!-- Source: https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api -->
        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
            <version>3.1.1</version>
            <scope>compile</scope>
        </dependency>

        <!-- Source: https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
            <version>4.0.6</version>
            <scope>compile</scope>
        </dependency>

単一パラメータに関わるバリデーション

指定したパラメータが特定の値であるかをチェックするアノテーションを作成します。

  • アノテーション
package nob.example.easyapp.validator;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

/**
 * サンプルのバリデーションです。
 *
 * @author nob
 */
@Documented
@Constraint(validatedBy = SampleValidator.class)
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Sample {

    /** エラーメッセージ */
    String message() default "バリデーションエラーが発生しました。";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
  • バリデーションの実装
package nob.example.easyapp.validator;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import lombok.NoArgsConstructor;

@NoArgsConstructor
public class SampleValidator implements ConstraintValidator<Sample, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {

        // ユーザ名"nob"は許可しない
        return !value.equals("nob");
    }
}

複数パラメータに関わるバリデーション

2つの日付の前後関係をチェックするアノテーションを作成します。

  • アノテーション
package nob.example.easyapp.validator;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

/**
 * サンプルのバリデーションです。
 *
 */
@Documented
@Constraint(validatedBy = { SampleValidator.class }) // バリデータクラスを指定
@Target({ ElementType.TYPE }) // クラスに対して付与することを宣言
@Retention(RetentionPolicy.RUNTIME)
public @interface Sample {

    /** 開始日 */
    String startDate();

    /** 終了日 */
    String endDate();

    /** エラーメッセージ */
    String message() default "サンプルエラー";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
  • バリデーションの実装
package nob.example.easyapp.validator;

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

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

/**
 * サンプルのバリデータクラスです。
 *
 */
@NoArgsConstructor
@AllArgsConstructor
public class SampleValidator implements ConstraintValidator<Sample, Object> {

    /** 開始日 */
    private String startDate;

    /** 終了日 */
    private String endDate;

    @Override
    public void initialize(Sample annotation) {
        this.startDate = annotation.startDate();
        this.endDate = annotation.endDate();
    }

    /**
     * 今回はクラスに対するアノテーションなので、第一引数の型をObjectとして、各フィールドの値が格納されるようにしています。
     * フィールドに対して付与するアノテーションの場合、例えばString型にすることでそのフィールドの値が格納されます。
     *
     */
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {

        BeanWrapper beanWrapper = new BeanWrapperImpl(value);
        String startDateStr = (String) beanWrapper.getPropertyValue(startDate);
        String endDateStr = (String) beanWrapper.getPropertyValue(endDate);

        boolean result = false;

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        try {
            Date startDate = simpleDateFormat.parse(startDateStr);
            Date endDate = simpleDateFormat.parse(endDateStr);
            // 開始日が終了日より前か同日であればOK
            if (startDate.compareTo(endDate) <= 0) {
                return true;
            }
        } catch (ParseException e) {
            // 日付の形式が不正な場合はチェックしない
            return true;
        }

        return result;
    }
}
  • テストクラス
package nob.example.easyapp.validator;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

/**
 * SampleValidatorのテストクラスです。
 *
 * @author nob
 */
public class SampleValidatorTest {

    @Test
    public void test() {

        SampleValidator sampleValidator = new SampleValidator("startDate", "endDate");
        assertTrue(sampleValidator.isValid(new SampleValidatorTestDto("2024-07-01", "2024-07-02"), null));
        assertFalse(sampleValidator.isValid(new SampleValidatorTestDto("2024-07-03", "2024-07-02"), null));
    }

    /**
     * SampleValidatorのテスト用dtoです。
     *
     * @param startDate 開始日
     * @param endDate   終了日
     */
    private record SampleValidatorTestDto(String startDate, String endDate) {
    }
}