我正在使用 Spring Boot 3.1.4(也在 3.0.5 上进行了测试)并注意到一些导致下游问题的问题。我不确定这是否是 SpringBoot 中的错误或我这边的误用(如果是,请告知应该采取什么适当的方式)
我有一个多租户应用程序,我需要为每个租户配置数据源。为此,我通过自动装配 bean
spring.datasource
使用了 DataSourceProperties
下的配置属性。
当我尝试获取在 spring.datasource.xa.properties
下定义的所有 XA 数据源属性时,它们会映射到 Map<String, String>
(例如,如果我在 spring.datasource.xa.properties.my-first-property=firstValue
中定义属性 application.properties
,它将被映射到映射为 my-first-property:firstValue
)。
当所有属性仅从
application.properties
读取时,就没有问题。
有趣的部分是当我通过环境变量覆盖一些配置属性时(例如,当我将应用程序部署到 Kubernetes 集群时就是这种情况)。通过环境变量定义的属性完全按照 SpringBoot 在宽松绑定部分here中建议的方式定义。
发生这种情况时,我在映射中得到“重复”键 - 一个键来自
application.properties
并按原样映射(驼峰式或短横线大小写,无论配置文件中定义什么),其他键全部小写,没有破折号。但这两个键都有在环境变量中定义的值(应该如此)。
因此,例如,如果我在
application.properties
参数 spring.datasource.xa.properties.my-first-property=firstValue
中定义,并且还将环境变量定义为 SPRING_DATASOURCE_XA_PROPERTIES_MYFIRSTPROPERTY=newValue
,则映射中将有两个条目 - my-first-value
和 myfirstvalue
,两者的值都是 newValue
。
我将分享演示这种行为的相关类:
@Configuration
public class MySimpleConfiguration {
@Autowired
DataSourceProperties dataSourceProperties;
@Bean
public String myDummyBean() {
Map<String, String> xaProperties = dataSourceProperties.getXa().getProperties();
System.out.println("XA PROPERTY:\n" + xaProperties);
return "myDummyBean";
}
}
spring.datasource.xa.properties.my-first-property=firstValue
spring.datasource.xa.properties.my-second-property=secondValue
我通过 IntelliJ IDEA 运行它,配置如下:https://i.sstatic.net/yrzAL.png
运行时得到的日志是:
2023-11-09 12:02:47,320 [,] DEBUG o.s.b.f.s.DefaultListableBeanFactory Creating shared instance of singleton bean 'myDummyBean'
XA PROPERTY:
{myfirstproperty=newFirstValue, my-first-property=newFirstValue, my-second-property=secondValue}
我希望环境变量中的值在
Map
中被覆盖,但只有一个键存在 - 一个包含在配置文件 application.properties
中。
为什么这会给我带来麻烦?当我提取所有 XA 属性并将它们传递给实际的 DB 驱动程序(在我的例子中为 IBM DB2 驱动程序)时,参数的自动绑定失败,但没有匹配的全小写且不带破折号的键。我通过从地图中明确删除这些属性来克服这个问题,但同样,为什么它们首先是“重复的”?
我研究过这个问题,似乎没有这样的方法。
TL;博士
(事实上,某些 Shell 可以支持环境变量名称中的连字符(请参阅此示例),但这违反了 POSIX 标准并且不可移植,更多信息 - 可以在不通知的情况下进行更改)
潜在的解决方法(我在我的案例中应用了)
在映射值结构(POJO,记录)内,我们“复制”配置映射键,然后在 Spring Boot 属性初始化中验证配置跳过重复项(如果它们具有相同的键和值)
@Configuration
@ConfigurationProperties("spring.datasource.xa.properties")
@Validated
public class XaConfigurationProperties {
private Map<String, XaProperties> = emptyMap();
public record XaProperties(@NotBlank String xaKey, @NotBlank String url, String username, String password) {}
public void setXaProperties(Map<String, XaProperties> xaProperties) {
if (isEmpty(properties)) {
this.properties = emptyMap();
}
Map<String, XaProperties> result = new HashMap<>();
for (var entry : properties.entrySet()) {
String configMapKey = entry.getKey();
XaProperties configMapValue = entry.getValue();
String currentXaKey = configMapValue.xaKey();
// validate whether we have already cached a config with the same xaKey and if so -
// verify the configs aren't diverged
XaProperties existingConfig = result.get(currentXaKey);
if (existingConfig != null) {
if (!existingConfig.equals(configMapValue)) {
throw new IllegalArgumentException(
"xaKey is duplicated, but the configuration values are different: configMapKey=%s, configMapXaProperties=[%s], existingXaProperties=[%s]"
.formatted(configMapKey, configMapValue, existingConfig));
}
continue; // if existingConfig equals to currentConfig - leave it as is
}
result.put(currentXaKey, configMapValue);
}
this.properties = unmodifiableMap(result);
}
和
application.yaml
看起来像:
spring.datasource.xa.properties:
my-first-property:
xa-key: my-first-property
username: batman
password: Gotham
my-second-property:
xa-key: my-second-property
username: freddy-crueger
xa-value-2: Springwood
这可以被环境变量覆盖
SPRING_DATASOURCE_XA_PROPERTIES_MYFIRSTPROPERTY_PASSWORD=Arkham