Spring 中的 Bind
Bind 是什么?
Spring Boot 的 Bind 机制主要负责将 外部配置(如 application.yml、application.properties、环境变量等)绑定到 Java 对象 中。
外部配置绑定优先级:
- 命令行参数(
java -jar app.jar --db.port=9000) - 操作系统环境变量(
export DB_PORT=9000) - jar 包外部的配置文件(
/config/application.yaml) - jar 包内部的配置文件(
resources/application.yml)
三种绑定方式
@Value
@Component
public class MyService {
@Value("${app.name:default-app}") // 支持默认值
private String appName;
@Value("${app.feature.enabled:false}")
private boolean featureEnabled;
@Value("#{systemProperties['user.home']}") // SpEL 表达式
private String userHome;
}
限制:
- 不支持 JSR-303 校验
- 不支持复杂对象绑定
- 不支持松散绑定
- 无法在配置文件中提示 IDE 支持
@ConfigurationProperties
@ConfigurationProperties( prefix = "")
外部配置文件:
myapp:
name: 'myapp'
server:
host: 1.1.1.1
port: 80
security:
username: admin
password: 123
配置方式
@Data
@Component
@ConfigurationProperties(prefix = "myapp")
public class MyAppProperties {
// 必须要提供 getter setter,可以使用 Lombook 简化
private String name;
private ServerConfig server;
private ServerSecurity security;
//嵌套配置 创建类, 必须是 public static
@Data
public static class ServerConfig {
private String host;
private Long port;
}
@Data
public static class ServerSecurity {
private String username;
private String password;
}
}
使用方式
@Configuration
public class AppConfig {
@Autowired
private MyAppProperties properties;
}
-
prefix 的使用: prefix 填入 要被装载的属性的前缀,如果有多个嵌套则可以
myapp.server.XXX.XXX来装载对应层级下的属性 -
松散绑定: Spring 可以自动将 配置属性的名词与定义的类中的名称进行自动映射 支持的类型名称:
- kebab-case (烤肉串式): 全小写,单词间用连字符分隔,如 my-variable-name
- snake_case (蛇形): 全小写,单词间用下划线分隔,如 my_variable_name
- UPPERCASE (全大写): 全部大写,通常用于常量,如 MY_CONSTANT
-
JSR-303 校验: 在类上添加 @Validated 开启校验
- @NotNull
- @Min()
- @Max()
- @NotBlank
- @Size(min = )
@Data @Component @ConfigurationProperties(prefix = "myapp") @Validated // <--- 开启第一层校验 public class MyAppProperties { @Valid // <--- 关键!告诉 Spring 钻进去校验内部属性 private ServerConfig server; // ... }
Binder API
使用 Binder API 读取 Environment 中的配置
Bind bind = Binder.get(environment);
读取单个值
// 读取单个值
String host = bind.bind("myapp.server.host", String.class).orElse("1.1.1.1");
读取单个值要写入完整的路径,并且使用点号分割
读取复杂类型 (List/Map)
如果在 application.yaml 里写了一个 List:
my-app:
whitelist:
- 192.168.1.1
- 10.0.0.1
需要使用 Bindable 包装器处理范型
List<String> ips =
binder.bind( "my-app.whitelist", Bindable.listOf(String.class) // <--- 关键!告诉 Binder 这是一个 List<String> ).orElse(Collections.emptyList());
如果是更复杂的 比如
routes:
- id: "order-service"
path: "/api/order"
url: "http://localhost:8081"
- id: "user-service"
path: "/api/user"
url: "http://localhost:8082"
private List<Map<Object, Object>> routes = new ArrayList<>();
1.方法一
@SuppressWarnings("unchecked")
List<Map<Object, Object>> routes = (List<Map<Object, Object>>) (List<?>)
binder.bind("gateway.routes", Bindable.listOf(Map.class))
.orElse(List.of());
2.方法二
List<?> routes = binder.bind("gateway.routes", Bindable.listOf(Map.class))
.orElse(List.of());
3.方法三
public class RouteConfig {
private String id;
private String path;
private String url;
}
List<RouteConfig> routes = binder.bind("gateway.routes",
Bindable.listOf(RouteConfig.class))
.orElse(List.of());
读取整个配置类
//后面的 XXX.class 是在告诉 “我要读取的是什么类型的配置请转换成这种类型 "
MyAppProperties properties = bind.bind("myapp", MyAppProperties.class).get();
创建本地 bind 并绑定
创建本地 bind 并添加配置
Binder binder = Binder.get(environment);
//添加 配置
1. 方法一
MapConfigurationPropertySource source1 = new MapConfigurationPropertySource(map1);
MapConfigurationPropertySource source2 = new MapConfigurationPropertySource(map2);
Binder localBind = new Binder(source1, source2);
2. 方法二
List<ConfigurationPropertySource> sources = new ArrayList<>();
sources.add(new MapConfigurationPropertySource(map1));
sources.add(new MapConfigurationPropertySource(map2));
sources.add(new MapConfigurationPropertySource(map3));
Binder localBind = new Binder(sources);
生产中建议
private Binder createLocalBinder(Map<String, Object>... configMaps) {
List<ConfigurationPropertySource> sources = new ArrayList<>();
for (Map<String, Object> map : configMaps) {
if (map != null && !map.isEmpty()) {
sources.add(new MapConfigurationPropertySource(map));
}
}
return new Binder(sources);
}
// 使用
Binder localBind = createLocalBinder(legacyMap, otherMap, thirdMap);
将 bind 中的值绑定类
//RouteDefinition.java
@Data
public class RouteDefinition {
private String id;
private String path;
private String url;
}
Map<String, Object> legacyMap = new HashMap<>();
legacyMap.put("id", "legacy-service-999");
legacyMap.put("path", "/api/old");
legacyMap.put("url", "http://192.168.1.100");
//Bind 无法直接绑定 Map 需要转换
MapConfigurationPropertySource legacyConfigurationMap = new MapConfigurationPropertySource(legacyMap);
Binder localBind = new Binder(legacyConfigurationMap);
//为什么这里是 "" 空字符串?
RouteDefinition legacyRoute = localBind.bind("", RouteDefinition.class).orElse(new RouteDefinition());
System.out.println(legacyRoute);
prefix + 要注入的字段名 = key 此时 key 与 字段名完全相同,所以使用空字符串 如果 legacyMap 中 put 时 key 是 legacy.route.id 那么在将 source 中的属性绑定到 RouteDefinition 时 prefix = “legacy.route”
三种绑定方式对比
| 特性 | @Value | @ConfigurationProperties | Binder API |
|---|---|---|---|
| 使用场景 | 1-2 个简单配置 | 结构化配置、多个相关配置 | 动态绑定、运行时绑定 |
| JSR-303 校验 | ❌ 不支持 | ✅ 支持(@Validated) | ✅ 支持 |
| 松散绑定 | ❌ 不支持 | ✅ 支持 | ✅ 支持 |
| 复杂对象 | ❌ 不支持 | ✅ 支持(嵌套对象) | ✅ 支持 |
| IDE 元数据支持 | ❌ 无提示 | ✅ 有提示(spring-configuration-metadata.json) | ❌ 无提示 |
| 类型安全 | ⚠️ 编译时不检查 | ✅ 强类型检查 | ✅ 强类型检查 |
| 集合类型 | ⚠️ 需要手动解析 | ✅ 自动绑定 List/Set/Map | ✅ 自动绑定(需要 Bindable) |
| 默认值 | ✅ ${prop:default} |
✅ 字段初始化或 @DefaultValue | ✅ .withDefaultValue() |
| 灵活性 | 低 | 中 | 高 |
- 优先使用 @ConfigurationProperties - 类型安全、IDE 支持、可校验
- 简单配置用 @Value - 如端口、开关等单个属性
- 动态配置用 Binder API - 如从数据库、Nacos 读取配置后绑定