近日,公司項目中使用攜程網的Apollo配置管理中心進行統一配置管理,爲了方便環境部署和運維,能避免不少配置問題致使的環境部署錯誤;不少網友估計都用過Apollo;java
在咱們項目組使用前作了一些預研,發現還須要解決鏈接池的熱刷新問題,不然意味着Apollo的portal界面上修改配置後還得重啓服務才能生效;spring
可能不少人會說,Apollo配置管理中心自己就支持配置的熱刷新,可是,這隻適用於普通應用場景(如一些不須要複雜的初始化操做的組件和spring-boot默認支持的組件);數據庫
對於Druid鏈接池、Jedis鏈接池、Lettuce鏈接池等等之類的組件,實現配置的熱刷新仍然須要本身作一些代碼適配;app
網上有熱心網友也給出了一些對通用配置的熱刷新代碼實現,這種方式對spring-boot默認集成的第三方組件是有效的,好比spring-boot 2.x的默認數據庫鏈接池 Hikari(其實查看源代碼就能發現,spring-boot爲它監聽了EnvironmentChangeEvent事件並實現了熱刷新邏輯);運維
那麼,對基於Apollo配置管理中心的Druid鏈接池、Jedis鏈接池、Lettuce鏈接池等等之類的客戶端組件熱刷新問題如何解決呢?ide
本文暫時只給出思路,詳細代碼後續會放出來;實現方法步驟以下:spring-boot
1. 編寫配置監聽器,並將Apollo客戶端的ConfigChangeEvent事件封裝爲EnvironmentChangeEvent事件,在Spring IOC容器中廣播出去;post
@Component public class ApolloConfigListener implements ApplicationContextAware { private static final Logger LOGGER = LoggerFactory.getLogger(ApolloConfigListener.class); @Autowired private ApplicationContext applicationContext = null; @Autowired private RefreshScope refreshScope = null; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @ApolloConfigChangeListener public void onChange(ConfigChangeEvent changeEvent) { this.refreshProperties(changeEvent); } public void refreshProperties(ConfigChangeEvent changeEvent) { this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys())); this.refreshScope.refreshAll(); } }
2. 使用 @ApolloConfig 或者API方式 ConfigService.getConfig(namespace)等獲取最新配置ui
3. 編寫Spring事件監聽器,實現以下接口ApplicationListener<EnvironmentChangeEvent>,ApplicationContextAware, BeanDefinitionRegistryPostProcessor等;this
須要記錄 BeanDefinitionRegistry 和 ApplicationContext;
在監聽到 EnvironmentChangeEvent事件中,進行以下邏輯處理:
判斷配置變化的key是否跟對應的鏈接池組件相關
若是不相關,則忽略;不然,刷新IOC容器,即經過記錄的 BeanDefinitionRegistry 引用刪除鏈接池對應的 BeanDefinition,並從新根據Apollo客戶端接收到的最新配置重建IOC容器中跟鏈接池相關的全部bean;而後,刷新DI,即掃描類路徑下引用的鏈接池相關Bean的類,刷新其依賴關係;
public abstract class AbstractRefreshListener implements ApplicationListener<EnvironmentChangeEvent>, ApplicationContextAware, BeanDefinitionRegistryPostProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRefreshListener.class); protected ApplicationContext applicationContext = null; protected BeanDefinitionRegistry registry = null; public static interface DIFilter { public boolean accept(Class<?> clazz); } @Override public final void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override public final void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { this.registry = registry; } @Override public final void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames(); LOGGER.info("----------------------------------------------------"); LOGGER.info("AbstractRefreshListener.postProcessBeanFactory() register beans:"); for (String beanDefinitionName : beanDefinitionNames) { LOGGER.info("beanDefinitionName=" + beanDefinitionName); } LOGGER.info( "AbstractRefreshListener.postProcessBeanFactory() register bean count=" + beanDefinitionNames.length); LOGGER.info("----------------------------------------------------"); } @Override public void onApplicationEvent(EnvironmentChangeEvent changeEvent) { if (this.needRefresh(changeEvent)) { this.refreshIOC(changeEvent); } } protected boolean needRefresh(EnvironmentChangeEvent changeEvent) { boolean flag = (changeEvent != null && this.registry != null); return flag; } protected abstract void refreshIOC(EnvironmentChangeEvent changeEvent); protected void refreshDI(List<Class<?>> classList, DIFilter filter) { classList = Optional.ofNullable(classList).orElse(new ArrayList<Class<?>>()); for (Class<?> item : classList) { try { if (filter != null && filter.accept(item)) { this.applicationContext.getBean(item); } } catch (Throwable th) { LOGGER.error("AbstractRefreshListener.refreshDI() Throwable", th); } } } }
這裏的代碼只是給出大概思路,具體實現你們能夠本身去編寫;
固然,雖然這種作法能夠解決熱刷新問題,也存在着一些問題,好比刷新過程當中會致使服務中斷;
但一般狀況下,鏈接池的地址變動一般只發生在部署過程先後,在環境部署完畢以後應該不會有這種場景,仍是有一些實用價值的;
若是你們有更好的方案,不防評論裏指點一下,謝謝你們......