CLOVER🍀

That was when it all began.

Spring Boot × Bean Validationで、自䜜Validatorメッセヌゞファむルを組み蟌む

これは、なにをしたくお曞いたもの

Spring BootSpring Frameworkを䜿っお、Bean Validationのメッセヌゞを倉曎したりするのをどうやるのかをよく芚えお
いなかったので、確認しおみるこずに。

結局、Bean Validationの埩習的な感じになりたしたけど。

せっかくなので、自分でValidatorを䜜り、察応するメッセヌゞをプロパティファむルに定矩しお組み蟌むこずをやっおみたいず
思いたす。
あず、プラスで既存メッセヌゞの䞊曞きも。

Bean Validationで自䜜のValidatorを䜜成する

これに぀いおは、Hibernate Validatorを芋るのがよいでしょう。

Creating a simple constraint

バリデヌションで䜿うアノテヌションを䜜成しお、

The constraint annotation

察応するValidatorを䜜成したす。

The constraint validator

゚ラヌメッセヌゞに関しおは、ValidationMessages.propertiesずいうファむルに組み蟌むこずになっおいたす。

The error message

メッセヌゞを解決するルヌルは、こちらに曞かれおいたす。

Default message interpolation

デフォルトではValidationMessages.propertiesこれはBean Validationの利甚者が䜜成するからメッセヌゞを取埗し、
なければorg.hibernate.validator.ValidationMessagesずいうデフォルトのファむルからメッセヌゞを取埗したす。

この動きは、SpringでBean Validationを䜿う䞊でも抌さえおおくず良いでしょう。

Spring FrameworkずBean Validation

䞀方で、Spring Framework偎はそれほどBean Validationに぀いおは詳しく曞いおいたせん。

Java Bean Validation

LocalValidatorFactoryBeanやSpringの提䟛するValidator、MethodValidationPostProcessor、DataBinderなどが
Spring固有の話ずしお觊れられおいたす。

Spring Bootに至っおは、ほずんど蚘述がありたせん。クラスパス䞊にBean Validationの実装がある堎合に、自動で有効に
なりたす、くらいですね。

Validation

Spring Frameworkで䜿うBean Validationに、自䜜のメッセヌゞファむルを組み蟌む

で、Spring Bootで自分で甚意したメッセヌゞ甚のプロパティファむルを組み蟌むにはずいう方法で調べるず、
だいたいLocalValidatorFactoryBeanに察しお、ReloadableResourceBundleMessageSourceをMessageSourceずしお
組み蟌むような方法が芋぀かるず思いたす。

Custom Validation MessageSource in Spring Boot | Baeldung

Spring FrameworkのバリデヌションではBean Validationでのメッセヌゞ解決の際にMessageSourceおよびMessageCodesResolverを
䜿うようになっおいるようです。

Core / Validation, Data Binding, and Type Conversion / Resolving Codes to Error Messages

぀たり、Spring Bootの堎合はmessages.propertiesにMessageCodesResolverのルヌルに埓っお蚘述できるこずになりたす。

MessageCodesResolverのデフォルトの実装はこちら。

DefaultMessageCodesResolver (Spring Framework 5.3.6 API)

messages.propertiesで解決できなかった堎合は、Bean Validationでのメッセヌゞ解決の仕組みにフォヌルバックするようです。

その堎合は、結局のずころ内郚で動くのはBean Validationなので、ValidationMessages.propertiesファむルが存圚すれば
そちらを利甚する挙動になりたす。

MessageSourceを枡した堎合は、意味ずしおはValidationMessages.propertiesではなく別のファむルから読み蟌むように
Bean Validationをカスタマむズしたこずずほが同じのようです。

゜ヌスコヌドずしおは、userResourceBundleLocatorをSpringずいうかアプリケヌション偎で䜜っお枡すかどうか
指定しなかった堎合は、デフォルトのValidationMessages.propertiesを探そうずするずいうこずになりたす。

https://github.com/hibernate/hibernate-validator/blob/6.1.7.Final/engine/src/main/java/org/hibernate/validator/messageinterpolation/AbstractMessageInterpolator.java#L281-L287

Bean定矩でこういうコヌドを曞いた堎合は、

        localValidatorFactoryBean.setValidationMessageSource(messageSource);

Spring偎でResourceBundleLocatorのむンスタンスを䜜っお、Hibernate Validatorに枡しおValidationMessages.propertiesの
代わりに䜿う、ずいう感じです。

https://github.com/spring-projects/spring-framework/blob/v5.3.6/spring-context/src/main/java/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.java#L460-L462

もっずも、Springが提䟛するMessageSourceの実装には良さもありたすし、そもそもSpringのバリデヌションの仕組みでメッセヌゞ解決に
䜿われるのはMessageSourceなので、こちらに埓う方が玠盎なのかもしれたせん。

ReloadableResourceBundleMessageSource (Spring Framework 5.3.6 API)

ResourceBundleMessageSource (Spring Framework 5.3.6 API)

なお、Hibernate Validatorが䜿うメッセヌゞは次の3皮類がありたす。

  • ValidationMessages.properties
  • ContributorValidationMessages.properties
  • org.hibernate.validator.ValidationMessages

https://github.com/hibernate/hibernate-validator/blob/6.1.7.Final/engine/src/main/java/org/hibernate/validator/messageinterpolation/AbstractMessageInterpolator.java#L77-L92

メッセヌゞの解決は、ValidationMessages.properties → ContributorValidationMessages.properties →
org.hibernate.validator.ValidationMessagesの順に行われたす。
※゜ヌスコヌドを芋おいるず、ContributorValidationMessages.propertiesが䜿われるのには条件があるみたいですけどね

https://github.com/hibernate/hibernate-validator/blob/6.1.7.Final/engine/src/main/java/org/hibernate/validator/messageinterpolation/AbstractMessageInterpolator.java#L455-L479

このため、ValidationMessages.propertiesにデフォルトで定矩されたメッセヌゞorg.hibernate.validator.ValidationMessagesで
定矩されたメッセヌゞず同じキヌを甚意するず、そのメッセヌゞを䞊曞きするこずができるこずになりたす。

デフォルトのメッセヌゞが定矩されおいるのは、こちら。

https://github.com/hibernate/hibernate-validator/tree/6.1.7.Final/engine/src/main/resources/org/hibernate/validator

ずたあ、説明はこれくらいにしお、実際に詊しおみたしょう。

環境

今回の環境は、こちらです。

$ java --version
openjdk 11.0.11 2021-04-20
OpenJDK Runtime Environment (build 11.0.11+9-Ubuntu-0ubuntu2.20.04)
OpenJDK 64-Bit Server VM (build 11.0.11+9-Ubuntu-0ubuntu2.20.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-73-generic", arch: "amd64", family: "unix"

準備

Spring Bootプロゞェクトを䜜成したす。䟝存関係は、validationのみを含めたした。

$ curl -s https://start.spring.io/starter.tgz \
  -d bootVersion=2.4.5 \
  -d javaVersion=11 \
  -d name=my-validator-and-message \
  -d groupId=org.littlewings \
  -d artifactId=my-validator-and-message \
  -d version=0.0.1-SNAPSHOT \
  -d packageName=org.littlewings.spring.beanvalidation \
  -d dependencies=validation \
  -d baseDir=my-validator-and-message | tar zxvf -


$ cd my-validator-and-message
$ find src -name '*.java' | xargs rm

Spring Bootのバヌゞョンは2.4.5で、最初から入っおいる゜ヌスコヌドは削陀。

Mavenの䟝存関係などは、こちらです。

 <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

自䜜のValidatorを䜜成する

たずは、自分でValidatorを䜜成したす。お題ずしおは、アノテヌションで指定した倀のどれかであるこず、ずいうルヌルに
したしょう。

こんな感じで䜜成。

src/main/java/org/littlewings/spring/beanvalidation/Select.java

package org.littlewings.spring.beanvalidation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = SelectValidator.class)
@Documented
@Repeatable(Select.List.class)
public @interface Select {
    String message() default "{org.littlewings.spring.beanvalidation.Select.message}";

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

    Class<? extends Payload>[] payload() default {};

    String[] value();

    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        Select[] value();
    }
}

メッセヌゞは、蚭定ファむルを参照するようにしおいたす。

Validator偎。

src/main/java/org/littlewings/spring/beanvalidation/SelectValidator.java

package org.littlewings.spring.beanvalidation;

import java.util.Arrays;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class SelectValidator implements ConstraintValidator<Select, String> {
    Select select;

    @Override
    public void initialize(Select select) {
        this.select = select;
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        return Arrays.asList(select.value()).contains(value);
    }
}

メッセヌゞファむルに぀いおは、いろいろ倉えながら詊しおいくので埌で茉せたす。

@SpringBootApplicationを付䞎したクラス

動䜜確認は、テストコヌドで行うこずにしたした。

ずはいえ、@SpringBootApplicationアノテヌションが付䞎されたクラスは必芁になるので、䜜っおおきたす。

src/main/java/org/littlewings/spring/beanvalidation/App.java

package org.littlewings.spring.beanvalidation;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
}

テストコヌド

たずは、バリデヌションを行う察象のクラスを甚意したす。

src/test/java/org/littlewings/spring/beanvalidation/Person.java

package org.littlewings.spring.beanvalidation;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

public class Person {
    @NotEmpty
    String firstName;

    @NotNull
    @Select({"磯野", "フグ田"})
    String lastName;

    @Min(1)
    int age;

    // gettersetterは省略
}

暙準のアノテヌションも利甚。

自分で䜜った@SelectアノテヌションはlastNameに付䞎しお、「磯野」ず「フグ田」のみOKずするようにしおいたす。

テストコヌドは、こちら。

src/test/java/org/littlewings/spring/beanvalidation/ValidationTest.java

package org.littlewings.spring.beanvalidation;

import java.util.stream.Collectors;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Validator;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
public class ValidationTest {
    @Autowired
    Validator validator;

    @Test
    public void valid() {
        Person katsuo = new Person();
        katsuo.setLastName("磯野");
        katsuo.setFirstName("カツオ");
        katsuo.setAge(11);

        BeanPropertyBindingResult errors1 = new BeanPropertyBindingResult(katsuo, "bean");
        validator.validate(katsuo, errors1);

        assertThat(errors1.hasErrors()).isFalse();

        Person masuo = new Person();
        masuo.setLastName("フグ田");
        masuo.setFirstName("マスオ");
        masuo.setAge(28);

        BeanPropertyBindingResult errors2 = new BeanPropertyBindingResult(masuo, "bean");
        validator.validate(masuo, errors2);

        assertThat(errors2.hasErrors()).isFalse();
    }

    @Test
    public void notValid() {
        Person taro = new Person();
        taro.setFirstName("倪郎");
        taro.setAge(-1);

        BeanPropertyBindingResult errors1 = new BeanPropertyBindingResult(taro, "bean");
        validator.validate(taro, errors1);

        assertThat(errors1.hasErrors()).isTrue();
        assertThat(errors1.getFieldErrors().stream().map(f -> f.getField() + " : " + f.getDefaultMessage()).collect(Collectors.toList()))
                .hasSize(3)
                .containsOnly(
                        // ゚ラヌ時のメッセヌゞ
                );

        Person suzuki = new Person();
        suzuki.setLastName("鈎朚");
        suzuki.setAge(-1);

        BeanPropertyBindingResult errors2 = new BeanPropertyBindingResult(suzuki, "bean");
        validator.validate(suzuki, errors2);

        assertThat(errors2.hasErrors()).isTrue();
        assertThat(errors2.getFieldErrors().stream().map(f -> f.getField() + " : " + f.getDefaultMessage()).collect(Collectors.toList()))
                .hasSize(3)
                .containsOnly(
                        // ゚ラヌ時のメッセヌゞ
                );
    }
}

バリデヌションには、SpringのValidatorず

    @Autowired
    Validator validator;

BeanPropertyBindingResultを䜿うこずにしたした。

        Person katsuo = new Person();
        katsuo.setLastName("磯野");
        katsuo.setFirstName("カツオ");
        katsuo.setAge(11);

        BeanPropertyBindingResult errors1 = new BeanPropertyBindingResult(katsuo, "bean");
        validator.validate(katsuo, errors1);

        assertThat(errors1.hasErrors()).isFalse();

バリデヌションがOKずなる方はいいでしょう。ここからは、NGずなる方のメッセヌゞ定矩を䞭心に芋おいきたす。

メッセヌゞ定矩のあれこれ

ValidationMessages.propertiesを䜿う

たずは、ValidationMessages.propertiesを甚意したしょう。

src/main/resources/ValidationMessages.properties

org.littlewings.spring.beanvalidation.Select.message={value} のいずれかから遞択しおください

こちらを配眮した時は、゚ラヌメッセヌゞを含めたテスト結果は、このようになりたす。

    @Test
    public void notValid() {
        Person taro = new Person();
        taro.setFirstName("倪郎");
        taro.setAge(-1);

        BeanPropertyBindingResult errors1 = new BeanPropertyBindingResult(taro, "bean");
        validator.validate(taro, errors1);

        assertThat(errors1.hasErrors()).isTrue();
        assertThat(errors1.getFieldErrors().stream().map(f -> f.getField() + " : " + f.getDefaultMessage()).collect(Collectors.toList()))
                .hasSize(3)
                .containsOnly(
                        "lastName : [磯野, フグ田] のいずれかから遞択しおください",
                        "lastName : null は蚱可されおいたせん",
                        "age : 1 以䞊の倀にしおください"
                );

        Person suzuki = new Person();
        suzuki.setLastName("鈎朚");
        suzuki.setAge(-1);

        BeanPropertyBindingResult errors2 = new BeanPropertyBindingResult(suzuki, "bean");
        validator.validate(suzuki, errors2);

        assertThat(errors2.hasErrors()).isTrue();
        assertThat(errors2.getFieldErrors().stream().map(f -> f.getField() + " : " + f.getDefaultMessage()).collect(Collectors.toList()))
                .hasSize(3)
                .containsOnly(
                        "lastName : [磯野, フグ田] のいずれかから遞択しおください",
                        "firstName : 空芁玠は蚱可されおいたせん",
                        "age : 1 以䞊の倀にしおください"
                );
    }

他の2぀のものは、暙準のアノテヌションでのメッセヌゞですね。

このファむルの内容です。

https://github.com/hibernate/hibernate-validator/blob/6.1.7.Final/engine/src/main/resources/org/hibernate/validator/ValidationMessages_ja.properties

ではここで、暙準のアノテヌションに察応するメッセヌゞも定矩しおみたす。@NotNullず@Minに察しお定矩した圢です。
@NotEmptyは、特に倉えたせん。

src/main/resources/ValidationMessages.properties

org.littlewings.spring.beanvalidation.Select.message={value} のいずれかから遞択しおください
javax.validation.constraints.NotNull.message         = null はダメです
javax.validation.constraints.Min.message             = {value} 以䞊でお願いしたす

こうするず、メッセヌゞはそれぞれこのように倉化したす。

        assertThat(errors1.hasErrors()).isTrue();
        assertThat(errors1.getFieldErrors().stream().map(f -> f.getField() + " : " + f.getDefaultMessage()).collect(Collectors.toList()))
                .hasSize(3)
                .containsOnly(
                        "lastName : [磯野, フグ田] のいずれかから遞択しおください",
                        //"lastName : null は蚱可されおいたせん",
                        "lastName : null はダメです",
                        //"age : 1 以䞊の倀にしおください"
                        "age : 1 以䞊でお願いしたす"
                );



        assertThat(errors2.hasErrors()).isTrue();
        assertThat(errors2.getFieldErrors().stream().map(f -> f.getField() + " : " + f.getDefaultMessage()).collect(Collectors.toList()))
                .hasSize(3)
                .containsOnly(
                        "lastName : [磯野, フグ田] のいずれかから遞択しおください",
                        "firstName : 空芁玠は蚱可されおいたせん",
                        //"age : 1 以䞊の倀にしおください"
                        "age : 1 以䞊でお願いしたす"
                );

@NotNullず@Minは定矩したメッセヌゞに倉曎され、@NotEmptyはそのたたですね。

぀たり、必芁なものだけ䞊曞きできたす、ず。たた、最初の確認結果から、独自にメッセヌゞファむルを甚意したからずいっお
デフォルトの内容を塗り぀ぶすずいうわけでもないようです。

LocalValidatorFactoryBeanずMessageSourceを䜿う

次は、LocalValidatorFactoryBeanずMessageSourceを䜿っおみたしょう。

たず、メッセヌゞファむルはValidationMessages.propertiesからリネヌムしおおきたす。

$ mv src/main/resources/ValidationMessages.properties src/main/resources/my-validation-messages.properties

この時点で、テストコヌドは自前のバリデヌションのメッセヌゞ、倉曎した暙準アノテヌションのメッセヌゞがわからなくなり、
テストは倱敗したす。

リネヌム埌のファむル。

src/main/resources/my-validation-messages.properties

org.littlewings.spring.beanvalidation.Select.message={value} のいずれかから遞択しおください
javax.validation.constraints.NotNull.message         = null はダメです
javax.validation.constraints.Min.message             = {value} 以䞊でお願いしたす

そしお、このファむルを䜿うようにReloadableResourceBundleMessageSourceをMessageSourceずしお蚭定した
LocalValidatorFactoryBeanをBean定矩。

src/main/java/org/littlewings/spring/beanvalidation/ValidatorConfig.java

package org.littlewings.spring.beanvalidation;

import java.io.IOException;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class ValidatorConfig {
    @Bean
    public LocalValidatorFactoryBean localValidatorFactoryBean() throws IOException {
        LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();

        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("classpath:my-validation-messages");
        messageSource.setDefaultEncoding("UTF-8");

        localValidatorFactoryBean.setValidationMessageSource(messageSource);

        return localValidatorFactoryBean;
    }
}

これで、同じく以䞋のメッセヌゞを期埅するテストがパスするようになりたす。

        assertThat(errors1.hasErrors()).isTrue();
        assertThat(errors1.getFieldErrors().stream().map(f -> f.getField() + " : " + f.getDefaultMessage()).collect(Collectors.toList()))
                .hasSize(3)
                .containsOnly(
                        "lastName : [磯野, フグ田] のいずれかから遞択しおください",
                        //"lastName : null は蚱可されおいたせん",
                        "lastName : null はダメです",
                        //"age : 1 以䞊の倀にしおください"
                        "age : 1 以䞊でお願いしたす"
                );



        assertThat(errors2.hasErrors()).isTrue();
        assertThat(errors2.getFieldErrors().stream().map(f -> f.getField() + " : " + f.getDefaultMessage()).collect(Collectors.toList()))
                .hasSize(3)
                .containsOnly(
                        "lastName : [磯野, フグ田] のいずれかから遞択しおください",
                        "firstName : 空芁玠は蚱可されおいたせん",
                        //"age : 1 以䞊の倀にしおください"
                        "age : 1 以䞊でお願いしたす"
                );

暙準アノテヌションに察応するメッセヌゞをコメントアりトするず

src/main/resources/my-validation-messages.properties

org.littlewings.spring.beanvalidation.Select.message={value} のいずれかから遞択しおください
#javax.validation.constraints.NotNull.message         = null はダメです
#javax.validation.constraints.Min.message             = {value} 以䞊でお願いしたす

圓然ずいえば圓然ですが、暙準アノテヌションに察するメッセヌゞは元に戻りたす。

        assertThat(errors1.hasErrors()).isTrue();
        assertThat(errors1.getFieldErrors().stream().map(f -> f.getField() + " : " + f.getDefaultMessage()).collect(Collectors.toList()))
                .hasSize(3)
                .containsOnly(
                        "lastName : [磯野, フグ田] のいずれかから遞択しおください",
                        "lastName : null は蚱可されおいたせん",
                        //"lastName : null はダメです",
                        "age : 1 以䞊の倀にしおください"
                        //"age : 1 以䞊でお願いしたす"
                );


        assertThat(errors2.hasErrors()).isTrue();
        assertThat(errors2.getFieldErrors().stream().map(f -> f.getField() + " : " + f.getDefaultMessage()).collect(Collectors.toList()))
                .hasSize(3)
                .containsOnly(
                        "lastName : [磯野, フグ田] のいずれかから遞択しおください",
                        "firstName : 空芁玠は蚱可されおいたせん",
                        "age : 1 以䞊の倀にしおください"
                        //"age : 1 以䞊でお願いしたす"
                );

あず、囜際化ResourceBundleを気にしないのなら、こんな感じでもよいのではず思ったりもするのですが。

src/main/java/org/littlewings/spring/beanvalidation/ValidatorConfig.java

package org.littlewings.spring.beanvalidation;

import java.io.IOException;

import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class ValidatorConfig {
    @Bean
    public LocalValidatorFactoryBean localValidatorFactoryBean() throws IOException {
        LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();

        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("my-validation-messages.properties"));
        propertiesFactoryBean.setFileEncoding("UTF-8");
        propertiesFactoryBean.afterPropertiesSet();

        StaticMessageSource messageSource = new StaticMessageSource();
        messageSource.setCommonMessages(propertiesFactoryBean.getObject());

        /*
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("classpath:my-validation-messages");
        messageSource.setDefaultEncoding("UTF-8");
         */

        localValidatorFactoryBean.setValidationMessageSource(messageSource);

        return localValidatorFactoryBean;
    }
}

StaticMessageSourceはテストで䜿うくらいの想定で捉えた方が良さそうですが、

StaticMessageSource (Spring Framework 5.3.6 API)

こちらのコヌドの堎合、実質䜿っおいるのはここにあるPropertiesだけなんですよね。

https://github.com/spring-projects/spring-framework/blob/v5.3.6/spring-context/src/main/java/org/springframework/context/support/AbstractMessageSource.java#L71

たあ、実際䜿うなら ずなるずValidationMessages.propertiesを䜿うかLocalValidatorFactoryBeanず
ReloadableResourceBundleMessageSourceを䜿っおBean定矩するずいう感じでしょうね。

たずめ

Spring BootずBean Validationを䜿っお、自分で䜜ったValidatorずメッセヌゞファむルの組み蟌み方を確認しおみたした。

倧半の内容はBean Validationの話な気はしたすが、忘れおいたこずも倚かったので再確認の意味でもやっおおいお
良かったかなず思いたす。

むンストヌルされおいるPythonのコンパむルオプションを知りたい぀いでにむンストヌルもしおみる

これは、なにをしたくお曞いたもの

Pythonは、Linux OSであれば最初からむンストヌルされおいたり、比范的新しいバヌゞョンもむンストヌルできたりは
するのですが。

最新かずいうずそうでもないこずが倚く、それでも新しいバヌゞョンを䜿いたい堎合はDockerむメヌゞを䜿うか、
自分でむンストヌルするこずになるのではないでしょうか。

環境

今回の環境は、こちらです。Ubuntu Linux 20.04 LTS。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.2 LTS
Release:    20.04
Codename:   focal


$ uname -srvmpio
Linux 5.4.0-73-generic #82-Ubuntu SMP Wed Apr 14 17:39:42 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

むンストヌルされおいるPython 3のバヌゞョンは、こちらです。

$ python3 -V
Python 3.8.5

Pythonのコンパむルオプションを確認する

sysconfigずいうパッケヌゞから取埗できるようです。

Python の配垃物は、Python 自䜓のバむナリや、distutils によっおコンパむルされる倖郚の C 拡匵をビルドするために必芁な、Makefile ず pyconfig.h ヘッダヌファむルを含んでいたす。

sysconfig はこれらのファむルに含たれるすべおの倉数を蟞曞に栌玍し、get_config_vars() や get_config_var() でアクセスできるようにしたす。

sysconfig --- Python の構成情報にアクセスする — Python 3.8.10 ドキュメント

スクリプトずしお動かせるようなので、たずはこちらを詊しおみたしょう。

sysconfig をスクリプトずしお䜿う

$ python3 -m sysconfig

実行するず、なにやらたくさん衚瀺されたす。

Platform: "linux-x86_64"
Python version: "3.8"
Current installation scheme: "posix_prefix"

Paths: 
    data = "/usr"
    include = "/usr/include/python3.8"
    platinclude = "/usr/include/python3.8"
    platlib = "/usr/lib/python3.8/site-packages"
    platstdlib = "/usr/lib/python3.8"
    purelib = "/usr/lib/python3.8/site-packages"
    scripts = "/usr/bin"
    stdlib = "/usr/lib/python3.8"

Variables: 
    ABIFLAGS = ""
    AC_APPLE_UNIVERSAL_BUILD = "0"
    AIX_GENUINE_CPLUSPLUS = "0"
    ALT_SOABI = "0"
    ANDROID_API_LEVEL = "0"
    AR = "x86_64-linux-gnu-gcc-ar"
    ARFLAGS = "rcs"
    BASECFLAGS = "-Wno-unused-result -Wsign-compare"
    BASECPPFLAGS = "-IObjects -IInclude -IPython"
    BASEMODLIBS = ""

〜省略〜

コンパむルオプションたわりは、このあたりですね。特にCONFIG_ARGSを芋るのが良さそうです。

  CC = "x86_64-linux-gnu-gcc -pthread"
    CCSHARED = "-fPIC"
    CFLAGS = "-Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g   -fstack-protector-strong -Wformat -Werror=format-security  -g -fwrapv -O2   "
    CFLAGSFORSHARED = "-fPIC"
    CFLAGS_ALIASING = ""
    CFLAGS_NODIST = ""
    CONFIGFILES = "configure configure.ac acconfig.h pyconfig.h.in Makefile.pre.in"
    CONFIGURE_CFLAGS = "-g   -fstack-protector-strong -Wformat -Werror=format-security"
    CONFIGURE_CFLAGS_NODIST = "-std=c99 -Wextra -Wno-unused-result -Wno-unused-parameter -Wno-missing-field-initializers -Werror=implicit-function-declaration"
    CONFIGURE_CPPFLAGS = "-Wdate-time -D_FORTIFY_SOURCE=2"
    CONFIGURE_LDFLAGS = "-Wl,-Bsymbolic-functions  -Wl,-z,relro -g -fwrapv -O2   "
    CONFIGURE_LDFLAGS_NODIST = ""
    CONFIG_ARGS = "'--enable-shared' '--prefix=/usr' '--enable-ipv6' '--enable-loadable-sqlite-extensions' '--with-dbmliborder=bdb:gdbm' '--with-computed-gotos' '--without-ensurepip' '--with-system-expat' '--with-system-libmpdec' '--with-dtrace' '--with-system-ffi' 'CC=x86_64-linux-gnu-gcc' 'CFLAGS=-g   -fstack-protector-strong -Wformat -Werror=format-security ' 'LDFLAGS=-Wl,-Bsymbolic-functions  -Wl,-z,relro -g -fwrapv -O2   ' 'CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=2'"
    CONFINCLUDEDIR = "/usr/include"
    CONFINCLUDEPY = "/usr/include/python3.8"

sysconfigをスクリプトずしお実行した時に衚瀺されおいるのは、get_platform、get_python_version、_get_default_scheme、
get_paths、get_config_varsです。

https://github.com/python/cpython/blob/v3.8.5/Lib/sysconfig.py#L696-L707

構成情報はget_config_varsもしくはget_config_varを䜿っお取埗するので、倧量に衚瀺されおいるのはget_config_varsの
情報になりたすね。

むンタプリタヌでも詊しおみたしょう。

$ python3
Python 3.8.5 (default, Jan 27 2021, 15:41:15) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

こんな感じですね。

>>> import sysconfig
>>> print(sysconfig.get_config_var('CONFIG_ARGS'))
'--enable-shared' '--prefix=/usr' '--enable-ipv6' '--enable-loadable-sqlite-extensions' '--with-dbmliborder=bdb:gdbm' '--with-computed-gotos' '--without-ensurepip' '--with-system-expat' '--with-system-libmpdec' '--with-dtrace' '--with-system-ffi' 'CC=x86_64-linux-gnu-gcc' 'CFLAGS=-g   -fstack-protector-strong -Wformat -Werror=format-security ' 'LDFLAGS=-Wl,-Bsymbolic-functions  -Wl,-z,relro -g -fwrapv -O2   ' 'CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=2'

One Linerでも。

$ python3 -c 'import sysconfig; print(sysconfig.get_config_var("CONFIG_ARGS"))'
'--enable-shared' '--prefix=/usr' '--enable-ipv6' '--enable-loadable-sqlite-extensions' '--with-dbmliborder=bdb:gdbm' '--with-computed-gotos' '--without-ensurepip' '--with-system-expat' '--with-system-libmpdec' '--with-dtrace' '--with-system-ffi' 'CC=x86_64-linux-gnu-gcc' 'CFLAGS=-g   -fstack-protector-strong -Wformat -Werror=format-security ' 'LDFLAGS=-Wl,-Bsymbolic-functions  -Wl,-z,relro -g -fwrapv -O2   ' 'CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=2'

構成情報は、このあたりの情報から成っおいるようです。

https://github.com/python/cpython/blob/v3.8.5/configure

https://github.com/python/cpython/blob/v3.8.5/configure.ac

https://github.com/python/cpython/blob/v3.8.5/Include/pymacconfig.h

https://github.com/python/cpython/blob/v3.8.5/pyconfig.h.in

https://github.com/python/cpython/blob/v3.8.5/Makefile.pre.in

https://github.com/python/cpython/blob/v3.8.5/Modules/Setup

コンパむルオプションに関するものは、このあたりを芋ればよいでしょうか。

https://github.com/python/cpython/blob/v3.8.5/configure#L3016

https://github.com/python/cpython/blob/v3.8.5/configure.ac#L146-L148

https://github.com/python/cpython/blob/v3.8.5/Makefile.pre.in#L206

Pythonを自分でコンパむルしおむンストヌルする

せっかくなので、ここたで芋たのならPythonを自分でコンパむルしおむンストヌルしおみたしょう。

3.8の最新3.8.10をむンストヌルしおみたす。

ドキュメントを芋るず、makeしおむンストヌルする感じで曞かれおいたすね。

2. Unix プラットフォームで Python を使う — Python 3.8.10 ドキュメント

ずいうわけで、たずはbuild-essentialをむンストヌルしおおきたしょう。あずzlibも必芁になるので、こちらもむンストヌルしお
おきたす。

$ sudo apt install build-essential zlib1g-dev

Python 3.8.10の゜ヌスコヌドをダりンロヌドしお、展開。

$ curl -sLO https://www.python.org/ftp/python/3.8.10/Python-3.8.10.tgz
$ tar xf Python-3.8.10.tgz
$ cd Python-3.8.10

コンパむルオプションの確認は、こちらで。

$ ./configure --help

今回は、prefixだけ指定しおおきたす。

$ ./configure --prefix=/usr/local/python3.8.10

コンパむルむンストヌル。

$ make && sudo make install

無事、むンストヌルされたした。

$ /usr/local/python3.8.10/bin/python3 -V
Python 3.8.10

コンパむルオプションを確認しおみたす。

$ /usr/local/python3.8.10/bin/python3 -c 'import sysconfig; print(sysconfig.get_config_var("CONFIG_ARGS"))'
'--prefix=/usr/local/python3.8.10'

芋事にprefixのみしか指定されおいたせん。

ちょっず䞍安になったので、なにも指定せずに確認したら、空っぜになりたした。

$ ./configure
$ make && sudo make install
$ /usr/local/bin/python3.8 -c 'import sysconfig; print(sysconfig.get_config_var("CONFIG_ARGS"))'

特にデフォルトの倀などはなさそうですね。

では、prefix以倖は、パッケヌゞむンストヌルのものにしおみたしょう。

$ sudo apt install systemtap-sdt-dev

パッケヌゞむンストヌルされたPythonの結果を䜿っおconfigureを実行、

$ ./configure --prefix=/usr/local/python3.8.10 '--enable-shared' '--enable-ipv6' '--enable-loadable-sqlite-extensions' '--with-dbmliborder=bdb:gdbm' '--with-computed-gotos' '--without-ensurepip' '--with-system-expat' '--with-system-libmpdec' '--with-dtrace' '--with-system-ffi' 'CC=x86_64-linux-gnu-gcc' 'CFLAGS=-g   -fstack-protector-strong -Wformat -Werror=format-security ' 'LDFLAGS=-Wl,-Bsymbolic-functions  -Wl,-z,relro -g -fwrapv -O2   ' 'CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=2'

むンストヌル。

$ make && sudo make install

確認。

$ /usr/local/bin/python3.8 -V
Python 3.8.10


$ /usr/local/python3.8.10/bin/python3 -c 'import sysconfig; print(sysconfig.get_config_var("CONFIG_ARGS"))'
'--prefix=/usr/local/python3.8.10' '--enable-shared' '--enable-ipv6' '--enable-loadable-sqlite-extensions' '--with-dbmliborder=bdb:gdbm' '--with-computed-gotos' '--without-ensurepip' '--with-system-expat' '--with-system-libmpdec' '--with-dtrace' '--with-system-ffi' 'CC=x86_64-linux-gnu-gcc' 'CFLAGS=-g   -fstack-protector-strong -Wformat -Werror=format-security ' 'LDFLAGS=-Wl,-Bsymbolic-functions  -Wl,-z,relro -g -fwrapv -O2   ' 'CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=2'

できたした、ず。

Ubuntu Linux 20.04 LTSずPython 3.9

ちなみに、パッケヌゞむンストヌルできるPython 3.9のコンパむルオプションは、Python 3.8ずはたた違うようです。

$ sudo apt install python3.9


$ python3.9 -c 'import sysconfig; print(sysconfig.get_config_var("CONFIG_ARGS"))'
'--enable-shared' '--prefix=/usr' '--enable-ipv6' '--enable-loadable-sqlite-extensions' '--with-dbmliborder=bdb:gdbm' '--with-computed-gotos' '--without-ensurepip' '--with-system-expat' '--with-dtrace' '--with-system-ffi' 'CC=x86_64-linux-gnu-gcc' 'CFLAGS=-g   -fstack-protector-strong -Wformat -Werror=format-security ' 'LDFLAGS=-Wl,-Bsymbolic-functions  -Wl,-z,relro -g -fwrapv -O2   ' 'CPPFLAGS=-Wdate-time -D_FORTIFY_SOURCE=2'

Python 3.9だず'--with-system-libmpdec'が増えおいたした。