Spring動態(tài)替換Properties配置變量
有如下需求撒汉,在Spring啟動的時候蟋字,我們需要修改某個指定的配置的值承粤,從而達到動態(tài)加載變量的效果:
@Value("${destination}")
public String destination;
1.實現(xiàn)方法有如下:
- 在對應(yīng)的Spring Bean實例化之后择葡,通過反射動態(tài)的修改實例的參數(shù)值
- 動態(tài)的修改PropertySource
具體的類如下:
public interface HelloService {
/**
* say hello
*/
void sayHello();
}
@Service
public class HelloServiceImpl implements HelloService{
@Value("${destination}")
private String destination;
@Override
public void sayHello() {
System.out.println("Hello, let us go to " + destination);
}
}
第一種方法實現(xiàn)
方式1:
定義一個應(yīng)用事件肴茄,監(jiān)聽?wèi)?yīng)用上下文刷新事件,然后我們可以通過這個事件拿到ApplicationContext上下文偏形,進而去獲取對應(yīng)的SpringBean构捡,然后進行反射賦值操作。
public class LocalApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
Object helloService = event.getApplicationContext().getBean(HelloService.class);
if (helloService instanceof HelloServiceImpl) {
try {
Field field = HelloServiceImpl.class.getDeclaredField("destination");
field.setAccessible(true);
ReflectionUtils.setField(field, helloService, "廣東深圳");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
}
有了這個監(jiān)聽事件壳猜,我們需要把這個監(jiān)聽器添加到Spring的監(jiān)聽器集合中,可以通過啟動類的啟動方法進行添加:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(DemoApplication.class);
app.addListeners(new LocalApplicationListener());
app.run(args);
}
}
方式2:
定義一個類實現(xiàn)BeanPostProcessor接口滑凉,在接口中進行反射操作统扳。BeanPostProcessor是Spring提供的一個Bean擴展接口,可以通過該接口實現(xiàn)一些Bean創(chuàng)建之前和創(chuàng)建之后的操作畅姊。
@Component
public class LocalBeanProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof HelloServiceImpl) {
try {
Field field = HelloServiceImpl.class.getDeclaredField("destination");
field.setAccessible(true);
ReflectionUtils.setField(field, bean, "廣東廣州");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
return bean;
}
}
第二種實現(xiàn)方法:
通過了解Spring的加載流程咒钟,我們得知Spring在上下文準備完畢(配置信息解析完畢并創(chuàng)建好了應(yīng)用上下文)之后,會調(diào)用推送一個ApplicationEnvironmentPreparedEvent
若未,從這個事件中朱嘴,我們可以獲取到ConfigurableEnvironment
對象,而ConfigurableEnvironment
對象可以獲取到MutablePropertySources
。對于MutablePropertySources
萍嬉,它包含了Spring的所有 的配置信息乌昔,包括我們啟動應(yīng)用的application.properties文件的配置信息。具體代碼如下:
public class LocalEnvironmentPrepareEventListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
MutablePropertySources propertySources = event.getEnvironment().getPropertySources();
for (PropertySource<?> propertySource : propertySources) {
boolean applicationConfig = propertySource.getName().contains("applicationConfig");
if (!applicationConfig) {
continue;
}
Object property = propertySource.getProperty("destination");
Map<String, OriginTrackedValue> source = (Map<String, OriginTrackedValue>) propertySource.getSource();
OriginTrackedValue originTrackedValue = source.get("destination");
OriginTrackedValue newOriginTrackedValue = OriginTrackedValue.of("中國香港", originTrackedValue.getOrigin());
source.put("destination", newOriginTrackedValue);
System.out.println(property);
}
}
}
同樣的我們需要把該監(jiān)聽器添加到Spring中:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(DemoApplication.class);
app.addListeners(new LocalEnvironmentPrepareEventListener());
app.run(args);
}
}
知名的分布式配置管理工具Apollo
就是通過反射來實現(xiàn)配置參數(shù)的動態(tài)修改的壤追,Apollo
實現(xiàn)了BeanPostProcessor接口磕道,這樣它就可以把所有的Bean和@Value的key的關(guān)系保存起來,類似于Map<String, List<Bean>>行冰,當(dāng)配置中心的配置被改動的時候溺蕉,就發(fā)一個通知給對應(yīng)的服務(wù),然后由服務(wù)自己去拉取配置參數(shù)悼做,重新賦值疯特。