如何在 Spring 环境中添加计算属性以便在属性文件中用作占位符?

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

我有一个以标准方式配置的 Spring Boot 应用程序:加载时,它从

application.yaml
文件中获取属性,并将它们注入到多个配置 bean 中,并用
@Component
@ConfigurationProperties
进行注释。

我需要自定义计算一些属性。这些属性与网络配置有关:子网掩码和地址、广播IP等。它们的数量相当多,它们因服务器而异,而且我们有很多服务器。我宁愿不因为这个原因而增加属性文件的数量。

我有一个公开网络属性的

LocalNetwork
bean,以及构建它的服务。现在我希望这些属性可以用作
application.yaml
:

中定义的其他属性中的占位符
hosts:
   my-first-host: ${local-network.local.ip}
   my-second-host: ${local-network.default-loopback.ip}
   my-third-host: ${local-network.local.broacast-ip}
somewhere:
   else:
      broadcast-to: ${hosts.my-third-host} 
      # I could do without this double indirection, 
      # but it would be so nice to have it.

我可以使用

LocalNetwork
bean 作为注入到其他 bean 的依赖项,但这会使这部分配置变得相当模糊。我更喜欢
application.yaml
显式传递一些可读的
${local-network.local.ip}
变量作为其他配置属性的值。

我想出了一种让它部分工作的方法:

@Configuration
@ComponentScan
public class ServicesConfiguration {

    @Bean
    @Primary
    public LocalNetwork networkInterface() {
        return LocalNetworkBuilder.build();
    }

    // ...
    // Lots of other things here
    // ...
        
    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(LocalNetwork localNetwork) {

        Properties properties = new Properties();
        properties.putIfAbsent("local-network.default-loopback.ip", 
                localNetwork.getLoopbackDefault().ip(0).getHostAddress());
        properties.putIfAbsent("local-network.local.ip", 
                localNetwork.getNetwork().ip(0).getHostAddress());
        properties.putIfAbsent("local-network.local.broadcast-ip", 
                localNetwork.getNetwork().broadcastIp().getHostAddress());

        PropertySourcesPlaceholderConfigurer p = new PropertySourcesPlaceholderConfigurer();
        p.setProperties(properties);

        return p;
    }
}

但我不喜欢它,有两个原因:

  • 它隐藏在未显式执行此任务的配置 bean 中。
  • broadcast-to: ${my-third-host}
    无法解析,出现错误 无法解析值“${my-third-host}”中的占位符“my-third-host”。不过它确实解决了
    my-third-host: ${local-network.local.broacast-ip}

实现这种占位符机制的正统方法是什么?

注意这不适用于服务器应用程序。这是一个测试应用程序,将由操作员安装在服务器上。操作员可能会根据自己的需要编辑外部

application.yaml
。这就是为什么保持属性文件可读非常重要。

N.B.2 我不喜欢直接使用

${local-network.local.broacast-ip}
,而是使用它来初始化
my-third-host
,然后在应用程序文件的其余部分中使用
${my-third-host}
,原因是为了可读性:所有操作员都熟悉 的名称整个公司使用的主机,因此他们可以更轻松地通过这种方式理解设置。

java spring spring-boot properties-file
1个回答
0
投票

事实证明,通过用

PropertySourcesPlaceholderConfigurer
替换默认构造函数来直接将属性添加到
@Bean
中可以以某种方式工作,但并不完全:

  • 添加的属性可供
    application.yaml
    使用,并且可以使用
    ${...}
  • 引用它们
  • 但它们并未完全融入财产解析过程

例如,考虑以下配置:

hosts:
   my-first-host: ${local-network.local.ip}
   ...
somewhere:
   else:
      broadcast-to: ${hosts.my-third-host}

如果

local-network.local.ip = 167.1.1.1
,则
hosts.my-first-host
正确设置为
167.1.1.1
,但
broadcast-to
设置为
local-network.local-ip
,无需进一步解析。

一种有效的方法是添加一个

ApplicationContextInitializer
,将属性添加为
PropertySource
:

public class NetworkPropertiesContextInitializer
        implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    private final LocalNetwork localNetwork;

    public NetworkPropertiesContextInitializer() {
        this.localNetwork = LocalNetworkBuilder.build();
    }

    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();

        // Create map for properties and add first (important)
        Map<String, Object> networkProperties = new HashMap<>();
        networkProperties.put("local-network.default-loopback.ip", 
                localNetwork.getLoopbackDefault().ip(0).getHostAddress());
        networkProperties.put("local-network.local.ip", 
                localNetwork.getNetwork().ip(0).getHostAddress());
        networkProperties.put("local-network.local.broadcast-ip", 
                localNetwork.getNetwork().broadcastIp().getHostAddress());

        // Add properties as a new property source:
        environment.getPropertySources().addFirst(
                new MapPropertySource("networkProperties",
                    networkProperties));

    }
}

然后您需要从应用程序的入口点引用上下文初始值设定项。

@SpringBootApplication(scanBasePackageClasses = {ServicesConfiguration.class})
public class Application {
    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class)
                .web(WebApplicationType.NONE)
                .initializers(new NetworkPropertiesContextInitializer())
                .run(args);
    }
}

从测试弹簧配置来看,它会是这样的:

@SpringBootTest(classes = ServicesConfiguration.class)
@ContextConfiguration(initializers = NetworkPropertiesContextInitializer.class)
@EnableAutoConfiguration
public class SpringIntegrationConfig {

}

参考资料:

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