Spring Boot 3 - 属性绑定到 Map“重复”键

问题描述 投票:0回答:1

我正在使用 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 驱动程序)时,参数的自动绑定失败,但没有匹配的全小写且不带破折号的键。我通过从地图中明确删除这些属性来克服这个问题,但同样,为什么它们首先是“重复的”?

spring-boot configuration property-binding
1个回答
0
投票

我研究过这个问题,似乎没有这样的方法。

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

© www.soinside.com 2019 - 2024. All rights reserved.