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应用程序的配置文件中,有以下类型。
@PropertySource
Spring Frameworkの設定ファイル(追加)@TestPropertySource
Spring 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
消息定义文件
在消息定义文件中,可以定义要在屏幕上显示的消息,并且可以实现消息的国际化。
阅读信息有两种方法。
-
- 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进行输入检查错误时显示的消息,并且可以进行消息的国际化。
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中进行自定义扩展。