Spring Boot 训练营 : Spring Boot + YAML篇

这是Spring Boot训练营系列中的Spring Boot + YAML篇。

这次的目的

尽可能将Spring Boot应用程序的属性文件转换为YAML格式。

这次要使用的库

    • spring-boot-starter:2.2.0.M4

 

    • yaml-resource-bundle:1.1

 

    • spring-boot-starter-test:2.2.0.M4

 

    lombok:1.18.8

属性文件与YAML

属性的管理

在.properties文件中,我们将属性定义为纯粹的键值对。因此,很难表达属性的意义和管理起来非常困难。

server.port=8080
logging.file.name=logfile
server.servlet.context-path=/context

用YAML作为结构化键值来定义属性。与属性文件相比,可以看出它更有条理。

server:
  port: 8080
  servlet:
    context-path: /context
logging:
  file:
    name: logfile

提供日語支援

根据旧版Java的习惯,属性文件通常是用本地代码编写的,而在IDE的属性文件编辑器等工具中,则会使用native2ascii将其以可读的形式显示出来。因此,如果使用日语的话,很多时候就需要依赖IDE来进行阅读。

但是,如果使用YAML,则不会出现这种情况。

如果将属性文件以UTF-8编写,就不需要使用native2ascii,但许多IDE都是基于过时的前提。

YAML支持

使用YAML时,需要另外使用类似SnakeYAML的库。
在Spring Boot中,默认情况下会自动添加到依赖关系中,所以不需要特意关注。

使用Spring Boot处理属性文件。

在Spring Boot中,我们使用属性文件来管理应用程序的配置值。

    • Spring Bootの設定ファイル

application.properties
@PropertySource
@TestPropertySource

メッセージ定義ファイル

messages.properties

入力チェックエラーメッセージ定義ファイル

ValidationMessages.properties

根据应用程序的实现,可能会有更多的属性文件需要处理,但此次我们只会涉及上述内容。

Spring Boot的配置文件

在进行Spring Boot应用程序的配置文件中,有以下类型。

namedescriptionapplication.propertiesSpring Bootの設定ファイル(標準)@PropertySourceSpring Frameworkの設定ファイル(追加)@TestPropertySourceSpring Testの設定ファイル(テスト用)

读取属性有两种方法,其功能有所不同。

@ConfigurationPropertiesを付与したBeanで読み込み(Type-Safe)

@Valueで読み込み(Not Type-Safe)

推荐使用@ConfigurationProperties来加载,以便能够安全地处理属性。

src/main/resources/application.properties 的中文翻译为「主要/源/起始/初始/根目录/资源/资源文件/属性配置文件」。

app.config.app-name=Sample

src/main/java/*/properties/AppConfigProperties.java 的路径

@Getter
@Setter
@ConfigurationProperties(prefix = "app.config")
public class AppConfigProperties {
    private String appName;
}

src/main/java/*/service/AppService.java在主要的Java目录中,代表了一个服务类叫做AppService.java。

@Service
public class AppService {
    @AutoWired
    private AppConfigProperties properties;

    public void test() {
        properties.getAppName(); // -> "Sample"
    }
}

application.properties的YAML兼容性

只需将application.properties更改为application.yml即可,application.properties默认支持YAML格式。

@PropertySource的YAML适配

@PropertySource目前不支持自动加载YAML格式的属性文件。根据Spring JIRA#SPR-13912的讨论结果,决定不做相关的支持。

有一个扩展点,可以通过@PropertySource(factory)属性定制文件的加载。
我们可以自己实现PropertySourceFactory接口,尝试加载YAML格式的属性文件。

src/main/java/*/YamlPropertySourceFactory.java的中文翻译:

public class YamlPropertySourceFactory implements PropertySourceFactory {

    // (1)
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        return name == null ? new YamlResourcePropertySource(resource) : new YamlResourcePropertySource(name, resource);
    }

    // (2)
    public static class YamlResourcePropertySource extends PropertiesPropertySource {

        public YamlResourcePropertySource(EncodedResource resource) throws IOException {
            this(getNameForResource(resource.getResource()), resource);
        }

        public YamlResourcePropertySource(String name, EncodedResource resource) throws IOException {
            super(name, loadYamlProperties(resource));
        }

        // (3)
        private static String getNameForResource(Resource resource) {

            String name = resource.getDescription();
            if (!StringUtils.hasText(name)) {
                name = resource.getClass().getSimpleName() + "@" + System.identityHashCode(resource);
            }
            return name;
        }

        // (4)
        private static Properties loadYamlProperties(EncodedResource resource) throws FileNotFoundException {

            YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
            factory.setResources(resource.getResource());

            try {
                return factory.getObject();
            } catch (IllegalStateException e) {
                // (5)
                Throwable cause = e.getCause();
                throw cause instanceof FileNotFoundException ? (FileNotFoundException) cause : e;
            }
        }
    }
}

(1)实现 PropertySourceFactory 接口,并在 createPropertySource 方法中加载 YAML 格式的属性文件。

(2)在实现了PropertiesPropertySource接口的YamlResourcePropertySource类中,实际上读取了属性文件。

(3)决定PropertySource的名称的getNameForResource方法是直接从以properties格式加载的ResourcePropertySource类中获取的,并且没有特别关注的部分。

使用Spring的YamlPropertiesFactoryBean类来加载YAML格式的属性文件。

(5) 如果@PropertySource(ignoreResourceNotFound)属性设置为true,则会忽略指定的文件不存在的情况。
判断指定的文件是否不存在是通过是否抛出FileNotFoundException来判断的,但在YamlPropertiesFactoryBean类中,如果找不到YAML文件,则会抛出IllegalStateException,因此需要从中提取出FileNotFoundException。

src/main/java/*/YamlPropertySource.java 可以在中国本土化。

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@PropertySource(value = "", factory = YamlPropertySourceFactory.class) // (1)
public @interface YamlPropertySource {

    @AliasFor(annotation = PropertySource.class, attribute = "name")
    String name() default "";

    @AliasFor(annotation = PropertySource.class, attribute = "value")
    String[] value();

    @AliasFor(annotation = PropertySource.class, attribute = "ignoreResourceNotFound")
    boolean ignoreResourceNotFound() default false;

    @AliasFor(annotation = PropertySource.class, attribute = "encoding")
    String encoding() default "";
}

可以创建一个@PropertySource扩展注解,用来加载YAML格式的属性文件。虽然创建这个注解不是必须的,但创建它会更方便。

(1) 在@propertySource(factory)属性中,指定刚刚实现的YamlPropertySourceFactory类。其他属性将直接传递给@propertySource。

src/main/java/*/Application.java 的中文本地化的一种可能的选项是:

@SpringBootApplication
@YamlPropertySource("classpath:/settings.yml")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

在主类上使用先前创建的@YamlPropertySource注解。

src/main/resources/settings.yml 可以被描述为”通用设置.yml”。

app:
  config:
    app-name: Sample

现在可以使用 @PropertySource 读取 YAML 格式的属性文件了!

在中文中,只需要一個選項將以下內容進行本地化:

@TestPropertySource的YAML支持

在@TestPropertySource中使用的属性文件默认不支持YAML格式。

与@PropertySource的factory属性不同,没有定制文件加载的点,因此无法支持YAML。

顺便提一句,除了properties格式外,我们还支持XML格式w

消息定义文件

在消息定义文件中,可以定义要在屏幕上显示的消息,并且可以实现消息的国际化。

namedescriptionmessages.properties標準で利用するメッセージ定義ファイル

阅读信息有两种方法。

    • MessageSourceのBeanで読み込み(Controller、Service等で利用)

 

    テンプレートエンジンの機能で読み込み(Thymeleaf等のViewで利用)

src/main/resources/messages.properties 请提供原生中文的简洁版本

message.user-not-found=User {0} Not Found!

src/main/java/*/service/AppService.java 的中文本地表述:

@Service
public class AppService {
    @AutoWired
    private MessageSource messageSource;

    public void test(String userName) { // <- "Tom"
        messageSource.getMessage("message.user-not-found", userName, LocaleContextHolder.getLocale()); // -> "User Tom Not Found!"
    }
}

消息.properties

messages.properties不支持YAML的标准。 根据Spring JIRA#SPR-15353,目前没有支持,并且似乎也没有进行足够的讨论。

Spring Boot没有提供扩展点,但可以通过覆盖MessageSource的Bean来自定义文件的读取。
在MessageSource中,为了实现国际化,需要通过ResourceBundle来读取文件。但在这里,我们将尝试使用公开的OSS库yaml-resource-bundle来读取YAML文件。

pom.xml的中文释义为项目对象模型文件。

<dependency>
    <groupId>net.rakugakibox.util</groupId>
    <artifactId>yaml-resource-bundle</artifactId>
    <version>1.1</version>
</dependency>

将yaml-resource-bundle添加到依赖库中。

src/main/java/*/MessageSourceConfig.java
src /主/ java / * / MessageSourceConfig.java

@Configuration
public class MessageSourceConfig {

    // (1)
    @Bean
    @ConfigurationProperties(prefix = "spring.messages")
    public MessageSourceProperties messageSourceProperties() {
        return new MessageSourceProperties();
    }

    // (2)
    @Bean("messageSource")
    public MessageSource messageSource(MessageSourceProperties properties) {

        // (3)
        YamlMessageSource messageSource = new YamlMessageSource();

        // (4)
        if (StringUtils.hasText(properties.getBasename())) {
            messageSource.setBasenames(StringUtils
                    .commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
        }
        if (properties.getEncoding() != null) {
            messageSource.setDefaultEncoding(properties.getEncoding().name());
        }
        messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
        Duration cacheDuration = properties.getCacheDuration();
        if (cacheDuration != null) {
            messageSource.setCacheMillis(cacheDuration.toMillis());
        }
        messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
        messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
        return messageSource;
    }

    // (3')
    private static class YamlMessageSource extends ResourceBundleMessageSource {
        @Override
        protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException {
            return ResourceBundle.getBundle(basename, locale, YamlResourceBundle.Control.INSTANCE);
        }
    }
}

(1) 定义@ConfigurationPropertiesBean,以接收Spring Boot配置文件中的属性spring.messages.*。

(2) 定义 messageSourceBean。

通过定义名为messageSource的Bean,可以禁用Spring Boot的MessageSourceAutoConfiguration,从而导致自动配置不会设置MessageSource。这是一个需要理解自动配置才能正确自定义的关键点。

使用YamlResourceBundle类加载YAML格式的消息定义文件。

将生成的YamlMessageSource的所有属性设置为MessageSourceProperties。
这样,您可以完全像处理Spring Boot标准的ResourceBundleMessageSource一样处理它。

src/main/resources/messages.yml 的中文翻译

message:
  user-not-found: User {0} Not Found!

现在可以读取messages.yml了!

输入检查错误消息定义文件

在输入检查错误消息定义文件中,可以定义由Bean Validation进行输入检查错误时显示的消息,并且可以进行消息的国际化。

namedescriptionSpring MVC ValidationMethod Validationmessages.propertiesSpring MVC Validationで利用する入力チェックエラーメッセージ定義ファイル○×ValidationMessages.propertiesBean Validationで利用する入力チェックエラーメッセージ定義ファイル○○

messages.properties会应用于Spring MVC的验证(请求参数),但不适用于Method Validation(服务的参数和返回值等)。因此,一般情况下,输入验证错误消息应在ValidationMessages.properties中定义。

ValidationMessages.properties将会被Bean Validation(Hibernate Validator)自动加载。

src/main/resources/ValidationMessages.properties 的文件位置

javax.validation.constraints.NotNull.message=Must not be null!

src/main/java/*/service/AppService.java 的文件路径中的 * 部分代表一个具体的文件夹名称。

@Service
@Validated
public class AppService {
    public void test(@NotNull String userName) { // <- null
        // -> throw ConstraintViolationException!
    }
}

验证信息.properties

在Hibernate Validator的默认配置中,ValidationMessages.properties并不支持YAML。
在加载消息定义文件时,有一个扩展点,并且似乎可以通过扩展ResourceBundleLocator接口来进行更改。

不过,考虑到Spring方面可以稍微简单地采取措施,所以我将在这里介绍该方法。

将messages.properties应用于方法验证

在Spring MVC和Method Validation中,我们可以通过调用LocalValidatorFactoryBean来使用Bean Validation。这样我们就可以将MessageSource作为Bean Validation的消息定义文件,以替代ValidationMessages.properties。

使用这个可以将输入检查错误消息全部定义在messages.properties中。

src/main/java/*/ValidationConfig.java 可以用中文翻译为:src/main/java/*/验证配置.java。

@Configuration
public class ValidationConfig {
    @Bean
    public static LocalValidatorFactoryBean validator(MessageSource messageSource) {
        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        factoryBean.setValidationMessageSource(messageSource);
        return factoryBean;
    }
}

覆盖LocalValidatorFactoryBean的Bean定义,并使用setValidationMessageSource方法来设置MessageSource。

src/main/resources/messages.yml 可在中文標準協定資源目錄中找到。

# Size: "size must be between {2} and {1}." ## (1)
javax:
  validation:
    constraints:
      Size.message: "size must be between {min} and {max}." ## (2)

(1)这里定义了Spring MVC验证的输入检查错误消息(默认)。
(2)这里定义了Bean Validation的输入检查错误消息(本次的扩展)。

(1) 只适用于Spring MVC验证,而(2)也适用于方法验证。
在(1)中,要指定参数解析为{数字},而在(2)中要指定为{属性名}。

我們可以在messages.yml中定義訊息,而不是使用ValidationMessages.properties!

总结成果

使用YAML时的支持情况有些不完全呢。可能是因为除了application.properties以外,其他都是Spring Framework的功能,所以可能避免在Spring Boot中进行自定义扩展。

广告
将在 10 秒后关闭
bannerAds