重写PropertySourcesPlaceholderConfigurer后RefreshScope失效问题排查
问题原因分析
你写的PasswordUpdatePropertyConfigurer继承了PropertySourcesPlaceholderConfigurer,这个类是BeanFactoryPostProcessor,它的loadProperties方法只会在Spring容器启动初期执行一次——把处理后的属性存入内部的Properties对象,后续所有依赖这些属性创建的Bean,都是从这个缓存的Properties里取值。
而Spring Cloud的/env和/refresh端点,本质是更新Environment中的属性源,但不会触发PropertySourcesPlaceholderConfigurer重新执行loadProperties方法。这就导致:更新后的属性不会被你的转换逻辑处理,而且Bean依然使用容器启动时缓存的旧属性值,自然看不到同步更新的效果。
正确解决方案
不要用PropertySourcesPlaceholderConfigurer来做属性转换,改用包装PropertySource的方式,让属性读取的整个链路都经过你的转换逻辑,包括/refresh之后的属性更新。
可以实现EnvironmentPostProcessor,在环境初始化时把原有的PropertySource替换为自定义包装类:
@Component @Slf4j public class PasswordUpdateEnvironmentPostProcessor implements EnvironmentPostProcessor { @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { MutablePropertySources propertySources = environment.getPropertySources(); List<PropertySource<?>> sourcesToWrap = new ArrayList<>(); // 筛选需要包装的可枚举PropertySource,避免重复包装 for (PropertySource<?> source : propertySources) { if (source instanceof EnumerablePropertySource && !(source instanceof WrappedPasswordPropertySource)) { sourcesToWrap.add(source); } } // 替换原PropertySource为包装后的版本 for (PropertySource<?> source : sourcesToWrap) { propertySources.replace(source.getName(), new WrappedPasswordPropertySource(source)); } } // 自定义包装类,对属性读取做转换 static class WrappedPasswordPropertySource extends EnumerablePropertySource<Object> { private final EnumerablePropertySource<?> delegate; public WrappedPasswordPropertySource(PropertySource<?> delegate) { super(delegate.getName(), delegate.getSource()); this.delegate = (EnumerablePropertySource<?>) delegate; } @Override public Object getProperty(String name) { Object value = delegate.getProperty(name); // 执行你的属性转换逻辑 if (value instanceof String strValue && StringUtils.isNotEmpty(strValue) && strValue.startsWith("password")) { return strValue.replace("password", "niti"); } return value; } @Override public String[] getPropertyNames() { return delegate.getPropertyNames(); } } }
方案优势
- 无论容器启动还是
/refresh更新属性后,只要读取属性就会经过WrappedPasswordPropertySource的getProperty方法,自动应用转换逻辑; - 如果Bean使用了
@RefreshScope注解,刷新后会重新读取Environment中的属性,自然能拿到转换后的新值; - 完全兼容Spring Cloud的配置刷新机制,不会出现属性不同步的问题。
内容的提问来源于stack exchange,提问作者Nitika Bansal
相关产品推荐
相关产品推荐

