Go-Spring 实战第 3 课 —— 复杂类型的配置绑定:Duration、Time、Slice、Map

上一篇我们讲了配置绑定。在配置从 Properties 进入业务结构体以后,应用代码拿到的就不再是一组字符串,而是已经转换过的 Go 值。

但真实服务的配置不会只停留在 stringintbool 等基础类型上。比如超时时间通常要绑定成 time.Duration,发布时间可能要绑定成 time.Time,服务地址可能是一组列表,多数据源、多 Redis、多 HTTP 客户端又经常按名称组织成一组实例。

如果这些复杂结构都需要交给业务代码自己解析,那么配置绑定只能算完成了一半。所以,Go-Spring 的配置绑定不只负责把单个 key 转成基础类型,还要支持时间、枚举、slice、map 等更复杂的类型。

基础类型

Go-Spring 开箱支持以下常见的基础类型。

  • 支持 bool 类型,支持 true/false1/0t/f 等多种常见写法。
  • 支持整数类型,支持 intint8int16int32int64 以及对应的无符号类型。
  • 支持浮点类型,支持 float32float64,支持科学计数法。
  • 支持 string 类型。

这些基础类型可以直接写在配置结构体字段上。

go 复制代码
type ServerConfig struct {
	Host      string `value:"${host:=localhost}"`
	Port      int    `value:"${port:=8080}"`
	EnableTLS bool   `value:"${enable-tls:=false}"`
}

时间类型

时间和时长是最常见的复杂配置。它们虽然经常以字符串形式写在配置文件里,但业务代码真正需要的是 time.Durationtime.Time。Go-Spring 内置了这两类转换器。

类型 用途 示例
time.Duration 时间时长 30s5m1h30m
time.Time 时间点 2006-01-022006-01-02 15:04:05

下面这个配置结构体把超时配置直接声明成 time.Duration,使用方式和基础类型完全一致。

go 复制代码
type Config struct {
	Host    string        `value:"${host:=localhost}"`
	Port    int           `value:"${port:=8080}"`
	Timeout time.Duration `value:"${timeout:=30s}"`
}

当我们在配置里写 timeout=5m 时,绑定后的 Timeout 字段就是 5 * time.Minute。业务代码不需要自己调用 time.ParseDuration,也不需要在每个模块里约定时长的表达格式。

自定义转换器

内置转换器覆盖不了所有的业务类型。像日志级别、运行模式、灰度状态,以及第三方库里的专用类型,往往都有自己的字符串格式。这时候需要通过自定义转换器,把这些类型接入到 Go-Spring 的配置绑定体系里面来。

下面这个示例展示了自定义转换器的使用。这里使用一个转换器把字符串转换成了枚举类型 Status。首先我们需要注册这个转换器。

go 复制代码
type Status int

const (
	StatusDisabled Status = 0
	StatusEnabled  Status = 1
)

func init() {
	conf.RegisterConverter(func(s string) (Status, error) {
		switch s {
		case "disabled", "off":
			return StatusDisabled, nil
		case "enabled", "on":
			return StatusEnabled, nil
		default:
			v, err := strconv.Atoi(s)
			if err != nil {
				return 0, err
			}
			return Status(v), nil
		}
	})
}

然后字段就可以直接声明成 Status 类型。

go 复制代码
type AppConfig struct {
	Status Status `value:"${app.status:=enabled}"`
}

此时,如果配置是 app.status=on,那么绑定后的 Status 就是 StatusEnabled。如果配置值既不是约定字符串,也不能转成数字,转换器会返回错误。然后这个错误会继续向外传播,应用也会在启动阶段失败。

注意,转换器应该在 init 阶段注册完成,这样业务 Bean 开始绑定配置之前,Go-Spring 已经知道该如何处理这个类型。

Slice

我们可以把一组相同类型的配置绑定成 slice。比如应用白名单、端口列表、下游端点列表,它们在业务代码里天然就是 []string[]int 或者 []EndpointConfig

对于支持列表的配置格式,可以直接写成自然的层级结构。

yaml 复制代码
apps:
  - a
  - b
  - c

对应的结构体只需要把字段声明成 slice。

go 复制代码
type AppConfig struct {
	Apps []string `value:"${apps}"`
}

绑定之后,Apps 的值就是 []string{"a", "b", "c"}

如果使用 properties 格式,也可以使用下标表达列表。

properties 复制代码
apps[0]=a
apps[1]=b
apps[2]=c

这种写法和 YAML 列表的语义一致,只是需要手动维护下标。

Go-Spring 要求下标从 0 开始并且连续。如果缺失了中间的下标,Go-Spring 只会绑定到最后连续的下标位置。

对于短字符串列表,我们还可以使用逗号分隔的写法。Go-Spring 会按逗号拆分,并对每个元素继续执行目标类型转换。

properties 复制代码
apps=a,b,c

这种写法更加紧凑,也适合环境变量或命令行参数这类不方便表达层级列表的来源。

如果列表元素本身是结构体,slice 仍然可以承接。比如下面的配置是把多个下游端点写成列表,每个元素都有自己的字段。

yaml 复制代码
endpoints:
  - name: user
    url: https://user.example.com
    timeout: 500ms
  - name: order
    url: https://order.example.com
    timeout: 1s

对应的结构体如下。

go 复制代码
type EndpointConfig struct {
	Name    string        `value:"${name}"`
	URL     string        `value:"${url}"`
	Timeout time.Duration `value:"${timeout:=1s}"`
}

type ClientConfig struct {
	Endpoints []EndpointConfig `value:"${endpoints}"`
}

绑定 Endpoints[0] 时,Go-Spring 会把父路径 endpoints[0] 和元素字段上的 nameurltimeout 组合起来。也就是说,Endpoints[0].Name 会读取 endpoints[0].name

总结一下就是,slice 负责表达"有多个",结构体字段负责表达"每个元素内部有哪些配置"。

Map

有些配置不是单纯的有序列表,而是按名字区分的一组实例。比如 masterslave 数据源、多个 Redis 客户端、不同业务线的 HTTP 客户端。

这类配置适合绑定成 map[string]T。比如下面这组 properties 把 masterslave 放在路径中间,然后这一层就会成为 map 的 key。

properties 复制代码
database.connections.master.host=localhost
database.connections.master.port=5432
database.connections.slave.host=replica
database.connections.slave.port=5433

对应的 Go 结构可以这样写。

go 复制代码
type DBConnectionConfig struct {
	Host string `value:"${host}"`
	Port int    `value:"${port:=5432}"`
}

type DatabaseConfig struct {
	Connections map[string]DBConnectionConfig `value:"${database.connections}"`
}

在实现配置绑定时,Go-Spring 会收集 database.connections 下面的第一层子 key。于是 masterslave 会成为 Connections 里的两个条目。

go 复制代码
cfg.Connections["master"].Host // localhost
cfg.Connections["slave"].Port  // 5433

接着,对 map value 的绑定会沿用结构体规则。比如对 master 这个条目来说,Host 字段实际读取的是 database.connections.master.hostPort 字段实际读取的是 database.connections.master.port

map[string]T 中的 T 既可以是基础类型,也可以是结构体,还可以继续组合其他复杂类型。

复杂类型绑定

复杂类型绑定让业务结构可以自然的承接配置结构,自定义转换器则把业务类型接入同一套绑定体系,进一步扩展了 Go-Spring 对配置语义的表达能力。

相关推荐
Jul1en_3 小时前
【Spring Cloud】Spring Cloud Config详解
后端·spring·spring cloud
霸道流氓气质11 小时前
基于 Milvus Lite 的 Spring AI RAG 向量库实践方案与示例
人工智能·spring·milvus
Ting-yu12 小时前
SpringCloud快速入门(7)---- 数据隔离
spring boot·spring·spring cloud
刀法如飞13 小时前
【Go 字符串查找的 20 种实现方式,用不同思路解决问题】
人工智能·算法·go
独自归家的兔14 小时前
OCPP 1.6 协议详解:GetLocalListVersion 获取本地列表版本指令
java·后端·物联网·spring·ocpp1.6
largecode17 小时前
如何让电话显示店名?来电显示店铺名称,提升有效接通率
java·开发语言·spring·百度·学习方法·业界资讯·twitter
xuhaoyu_cpp_java17 小时前
SpringMVC学习(五)
java·开发语言·经验分享·笔记·学习·spring
止语Lab17 小时前
别急着拆微服务:Go 项目演进的三个关键决策
go
勿忘,瞬间19 小时前
Spring IOC and DI
java·spring