【问题标题】:Spring Boot - Property placeholders remain unresolved despite setting values with System.setPropertySpring Boot - 尽管使用 System.setProperty 设置值,但属性占位符仍未解析
【发布时间】:2023-01-31 08:28:48
【问题描述】:

我有一个带有构造函数的 bean,如下所示。 password 参数是从占位符 my.password 解析而来的,默认值为 DEFAULT。如果传递了 DEFAULT 的值,则会记录一条警告。注意 - 此 Bean 包含在导入的第三方库中。

@Bean
public class EncryptionBean {
    public EncryptionBean(@Value("${my.password}") String password) {
        if "DEFAULT".equals(password) {
            // log warning message
        } else {
            // do stuff with the password
        }

    }
}

密码是在启动时使用客户端 SDK 从外部系统检索的。此 SDK 对象本身作为 Bean 提供(也来自第三方库)。检索密码后,我将其设置为上述EncryptionBean 的系统属性,以便在实例化时访问:

@Configuration
public class MyConfiguration {

    @Autowired
    public SDKObject sdkObject;

    @PostConstruct
    public void init() {
        System.setProperty("my.password", sdkObject.retrievePassword());

        // @Value("${my.password"}) should now be resolvable when EncryptionBean is instantiated
    }

}

但是,EncryptionBean 仍在为 my.password 实例化,值为 DEFAULT。我想知道 @PostConstruct 中的 System.setProperty 是否可能在 Spring 已经实例化 EncryptionBean 的实例之后执行?

如果是这样,有没有办法保证在 Spring 实例化 EncryptionBean 之前设置此属性?我发现 @DependsOn 是一种控制 Spring 实例化 Beans 的顺序的方法,但由于 EncryptionBean 来自第三方库,我无法使此注释起作用。

【问题讨论】:

    标签: java spring-boot


    【解决方案1】:

    System.setProperty() 设置系统属性,而 @Valueapplication.properties 文件加载属性。所以你的实现从根本上是不正确的。

    在没有大量样板文件的情况下,我想不出一种在运行时注入属性的干净利落的方法。但我想出了一个干净的方法来做你想做的事情,而不必刷新应用程序上下文或弄乱第 3 方库的实现。

    首先,我们从我们的应用程序上下文中排除第 3 方 bean:

    @ComponentScan(excludeFilters = @ComponentScan.Filter(value = EncryptionBean.class, type = FilterType.ASSIGNABLE_TYPE))
    @SpringBootApplication
    public class SandboxApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SandboxApplication.class, args);
        }
    
    }
    

    然后我们用我们想要的值自己创建 Bean。

    @Configuration
    public class MyConfiguration {
    
        public final SDKObject sdkObject;
    
        public MyConfiguration(SDKObject sdkObject) {
            this.sdkObject = sdkObject;
        }
    
        @Bean
        public EncryptionBean encryptionBean() {
            return new EncryptionBean(sdkObject.retrievePassword());
        }
    
    }
    

    【讨论】:

    【解决方案2】:

    您应该创建一个 Spring EnvironmentPostProcessor 类来从外部源检索密码并将其添加到 Spring Environment,而不是设置系统属性。这看起来像这样:

    public class PasswordEnvironmentPostProcessor implements EnvironmentPostProcessor, ApplicationContextAware {
    
        private ApplicationContext applicationContext;
    
        @Override
        public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
            SDKObject sdkObject = applicationContext.getBean(SDKObject.class);
            Map<String, Object> properties = Collections.singletonMap("my.password", sdkObject.retrievePassword());
            MapPropertySource propertySource = new MapPropertySource("password", properties);
            environment.getPropertySources().addFirst(propertySource);
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    }
    

    然后,您需要通过向文件 META-INF/spring.factories 添加一个条目来向 Spring 注册此类,如下所示:

    org.springframework.boot.env.EnvironmentPostProcessor=com.example.PaswordEnvironmentPostProcessor
    

    此处提供了相关文档:https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.application.customize-the-environment-or-application-context

    【讨论】:

      猜你喜欢
      • 2017-09-15
      • 2014-07-16
      • 2014-03-23
      • 1970-01-01
      • 1970-01-01
      • 2021-03-08
      • 1970-01-01
      • 2018-02-20
      • 2015-05-22
      相关资源
      最近更新 更多