Spring Boot:System.setProperty设置属性后第三方Bean占位符未解析
我有一个来自第三方库的EncryptionBean,其构造函数通过@Value("${my.password:DEFAULT}")解析占位符获取password参数,默认值为DEFAULT,若传入DEFAULT则会记录警告。
该Bean代码如下:
@Bean public class EncryptionBean { public EncryptionBean(@Value("${my.password}") String password) { if ("DEFAULT".equals(password)) { // 记录警告信息 } else { // 使用密码执行操作 } } }
密码在启动时通过第三方库提供的SDKObject Bean从外部系统获取,我在自定义配置类MyConfiguration的@PostConstruct方法中调用System.setProperty设置该属性,期望EncryptionBean实例化时能解析到该值:
@Configuration public class MyConfiguration { @Autowired public SDKObject sdkObject; @PostConstruct public void init() { System.setProperty("my.password", sdkObject.retrievePassword()); // 期望EncryptionBean实例化时@Value("${my.password}")能被解析 } }
但EncryptionBean实例化时仍获取到my.password的默认值DEFAULT。经排查,@PostConstruct中的System.setProperty执行时机晚于EncryptionBean的实例化。由于EncryptionBean来自第三方库,无法使用@DependsOn注解控制顺序,请问如何保证该属性在Spring实例化EncryptionBean之前就已设置?
1. 利用ApplicationContextInitializer提前设置系统属性
ApplicationContextInitializer是Spring启动阶段最早执行的扩展点之一,执行时机早于所有Bean的实例化。通过它可以提前获取SDK并设置系统属性:
public class PasswordInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { SDKObject sdkObject = applicationContext.getBean(SDKObject.class); String password = sdkObject.retrievePassword(); System.setProperty("my.password", password); } }
注册方式二选一:
- 在
resources/META-INF/spring.factories中添加:org.springframework.context.ApplicationContextInitializer=com.yourpackage.PasswordInitializer - 或者在SpringBoot启动类中手动添加:
public static void main(String[] args) { SpringApplication app = new SpringApplication(YourApplication.class); app.addInitializers(new PasswordInitializer()); app.run(args); }
2. 自定义PropertySource注入属性
直接将从SDK获取的密码注入到Spring的属性环境中,优先级高于系统属性,确保@Value解析时能拿到正确值:
@Configuration public class CustomPropertySourceConfig { @Autowired private SDKObject sdkObject; @Bean public BeanPostProcessor propertySourceAdder() { return new BeanPostProcessor() { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof ConfigurableEnvironment) { ConfigurableEnvironment env = (ConfigurableEnvironment) bean; String password = sdkObject.retrievePassword(); Map<String, Object> props = Collections.singletonMap("my.password", password); // 将自定义属性源放在最前面,确保优先级最高 env.getPropertySources().addFirst(new MapPropertySource("customPasswordSource", props)); } return bean; } }; } }
3. 使用BeanFactoryPostProcessor配合@AutoConfigureAfter
BeanFactoryPostProcessor的执行时机早于所有Bean实例化,结合@AutoConfigureAfter确保自定义配置在第三方加密配置之前执行:
@Configuration @AutoConfigureAfter(ThirdPartyEncryptionAutoConfiguration.class) // 替换ThirdPartyEncryptionAutoConfiguration为第三方库中实际的自动配置类 public class MyConfiguration { @Bean public static BeanFactoryPostProcessor passwordBeanFactoryPostProcessor() { return beanFactory -> { SDKObject sdkObject = beanFactory.getBean(SDKObject.class); String password = sdkObject.retrievePassword(); System.setProperty("my.password", password); }; } }
内容的提问来源于stack exchange,提问作者makazee

