【译】Spring Boot 2.0 中的属性绑定


description: "在 Spring Boot 2.0 中,绑定方式被重新设计,引入了新的抽象和全新的绑定 API。本文将介绍这些新类和接口的作用及其使用方法。"
date: 2024.10.27 10:26
categories:
- Spring
tags: [Spring Boot]
keywords: Spring Boot, relaxed binding, ConfigurationPropertySource, ConfigurationPropertyName, Origin, Binder, Bindable, BindResult, BindHandler, @ConfigurationProperties
cover: /contents/covers/property-binding-in-spring-boot-2-0.png


自 Spring Boot 的第一个版本发布以来,就可以使用 @ConfigurationProperties 注解将属性绑定到类。还可以用不同的形式指定属性名称。例如,person.first-nameperson.firstNamePERSON_FIRSTNAME 都可以互换使用。我们称这个功能为“宽松绑定”(relaxed binding)。

不幸的是,在 Spring Boot 1.x 中,“宽松绑定”有点过于宽松。很难准确定义绑定规则以及何时可以使用特定格式。我们还开始收到一些很难用 1.x 实现修复的问题报告。例如,在 Spring Boot 1.x 中,无法将属性绑定到 java.util.Set

因此,在 Spring Boot 2.0 中,我们开始重新设计绑定方式。我们添加了几个新的抽象,开发了一个全新的绑定 API。在本文中,我们将介绍一些新的类和接口,描述加入它们的原因、它们的作用以及如何在自己的代码中使用它们。

属性源(Property Sources)

如果你已经使用 Spring 一段时间,你可能熟悉 Environment 抽象。这个接口是一个 PropertyResolver,允许你从一些底层的 PropertySource 实现中解析属性。

Spring 框架为常见的事物提供了 PropertySource 实现,例如系统属性、命令行标志和 properties 文件。Spring Boot 会自动配置这些实现,以一种对大多数应用程序有意义的方式(例如,加载 application.properties)。

配置属性源(Configuration Property Sources)

Spring Boot 2.0 引入了一个新的 ConfigurationPropertySource 接口,而不是直接使用现有的 PropertySource 接口进行绑定。我们引入了一个新接口,以便为实现之前属于绑定器一部分的宽松绑定规则提供一个逻辑位置。

这个接口主要的 API 很简单:

ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name);

还有一个 IterableConfigurationPropertySource 变体,它实现了 Iterable<ConfigurationPropertyName>,因此您可以探索源中包含的所有名称。

您可以使用以下代码将 Spring Environment 适配为 ConfigurationPropertySources

Iterable<ConfigurationPropertySource> sources =
    ConfigurationPropertySources.get(environment);

如果您需要,我们还提供了一个简单的 MapConfigurationPropertySource 实现。

配置属性名(Configuration Property Names)

事实证明,如果将宽松属性名称的概念限制为一个方向,实现起来会容易得多。无论属性在底层源中如何表示,您都应该始终使用规范形式在代码中访问属性。

ConfigurationPropertyName 类强制执行这些规范命名规则,基本上可以归结为“使用小写端横线命名法(kebab-case)”。

因此,例如,即使底层源中使用 person.firstNamePERSON_FIRSTNAME,您也应该在代码中将属性引用为 person.first-name

Origin 支持

正如您所期望的那样,从 ConfigurationPropertySource 返回的 ConfigurationProperty 对象封装了实际的属性值,但它还可以包含一个可选的 Origin 对象。

Origin 是 Spring Boot 2.0 中引入的一个新接口,它允许您准确定位值的加载位置。有许多 Origin 实现,其中可能最有用的是 TextResourceOrigin。它提供了加载的 Resource 的详细信息,以及值的行和列号。

对于 .properties.yaml 文件,我们编写了自定义加载器,以跟踪加载文件时的来源。一些已经存在的 Spring Boot 功能特性也已被改进以利用来源信息。例如,绑定验证异常现在显示无法绑定的值和来源。以下是失败分析器显示错误的方式:

*************************** APPLICATION FAILED TO START ***************************

Description:

Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'person' to scratch.PersonProperties failed:

Property: person.name
Value: Joe
Origin: class path resource \[application.properties\]:1:13
Reason: length must be between 4 and 2147483647

Action:

Update your application's configuration

Binder API

Binder 类(在 org.springframework.boot.context.properties.bind 包中)允许您从一个或多个 ConfigurationPropertySource 中绑定某些内容。更准确地说,Binder 接受一个 Bindable 并返回一个 BindResult

Bindable

Bindable 可能是一个现有的 Java bean、一个 class 类型或一个复杂的 ResolvableType(例如 List<Person>)。以下是一些示例:

Bindable.ofInstance(existingBean);
Bindable.of(Integer.class);
Bindable.listOf(Person.class);
Bindable.of(resovableType);

Bindable 还用于携带注解信息,但通常您不需要担心这一点。

BindResult

与直接返回绑定对象不同,bind 方法返回一个称为 BindResult 的东西。类似于 Java 8 的 Streams 返回 OptionalBindResult 表示可能已绑定或未绑定的内容。

如果尝试获取未绑定对象的实际结果,将抛出异常。我们还提供了一些方法,让您在未绑定时提供替代值或映射到不同类型:

var bound = binder.bind("person.date-of-birth",
        Bindable.of(LocalDate.class));

// Return LocalDate or throws if not bound
bound.get();

// Return a formatted date or "No DOB"
bound.map(dateFormatter::format).orElse("No DOB");

// Return LocalDate or throws a custom exception
bound.orElseThrow(NoDateOfBirthException::new);

格式化和转换

大多数 ConfigurationPropertySource 实现将其底层值暴露为字符串。当 Binder 需要将源值转换为不同类型时,它会委托给 Spring 的 ConversionService API。如果需要调整值的转换方式,可以自由使用 @NumberFormat@DateFormat 等格式化注解。

Spring Boot 2.0 还引入了一些新的适用于绑定的注解和转换器。例如,现在可以将 4s 之类的值转换为 Duration。查看 org.springframework.boot.convert 包以获取详细信息。

BindHandler

有时,您可能需要在绑定时实现额外的逻辑,BindHandler 接口提供了一个很好的方法来实现这一点。每个 BindHandler 都有 onStartonSuccessonFailureonFinish 方法,可以实现以覆盖行为。

Spring Boot 提供了许多 handler,主要是为了支持现有的 @ConfigurationProperties 绑定。例如,ValidationBindHandler 可用于在绑定对象上应用 Validator 验证。

@ConfigurationProperties

正如本文开头所提到的,自 Spring Boot 诞生以来,@ConfigurationProperties 就是一个重要的特性。很可能 @ConfigurationProperties 将继续是大多数人执行绑定的方式。

尽管我们重新编写了整个绑定过程,但大多数人在升级 Spring Boot 1.5 应用程序时似乎没有遇到太多问题。只要您遵循 迁移指南 中的建议,您应该会发现一切正常。如果在升级应用程序时遇到问题,请在 GitHub 问题跟踪器 上报告,并附上一个重现问题的小示例。

未来工作

我们计划在 Spring Boot 2.1 中继续开发 Binder,我们希望支持的第一个功能是不可变配置属性。如果当前需要 getter 和 setter 的配置属性可以使用基于构造函数的绑定,那将非常好:

public class Person {

    private final String firstName;
    private final String lastName;
    private final LocalDateTime dateOfBirth;

    public Person(String firstName, String lastName,
            LocalDateTime dateOfBirth) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.dateOfBirth = dateOfBirth;
    }

    // getters

}

我们认为构造函数绑定也将与 Kotlin 数据类 非常好地配合使用。如果您对此功能的进展感兴趣,请订阅 issue #8762

总结

我们希望您发现 Spring Boot 2.0 中的新绑定功能有用,并希望您考虑升级现有的 Spring Boot 应用程序。

如果您想讨论绑定的一般问题,或者有特定的增强建议或问题,请 加入我们在 Gitter 上的讨论

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容