您好,登錄后才能下訂單哦!
小編這次要給大家分享的是apollo與springboot如何集成實現動態刷新配置,文章內容豐富,感興趣的小伙伴可以來了解一下,希望大家閱讀完這篇文章之后能夠有所收獲。
分布式apollo簡介
Apollo(阿波羅)是攜程框架部門研發的開源配置管理中心,能夠集中化管理應用不同環境、不同集群的配置,配置修改后能夠實時推送到應用端,并且具備規范的權限、流程治理等特性。
本文主要介紹如何使用apollo與springboot實現動態刷新配置,如果之前不了解apollo可以查看如下文檔
https://github.com/ctripcorp/apollo
學習了解一下apollo,再來查看本文
正文
apollo與spring實現動態刷新配置本文主要演示2種刷新,一種基于普通字段刷新、一種基于bean上使用了@ConfigurationProperties刷新
1、普通字段刷新
a、pom.xml配置
<dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-client</artifactId> <version>1.6.0</version> </dependency>
b、客戶端配置AppId,Apollo Meta Server
此配置有多種方法,本示例直接在application.yml配置,配置內容如下
app:
id: ${spring.application.name}
apollo:
meta: http://192.168.88.128:8080,http://192.168.88.129:8080
bootstrap:
enabled: true
eagerLoad:
enabled: true
c、項目中啟動類上加上@EnableApolloConfig注解,形如下
@SpringBootApplication @EnableApolloConfig(value = {"application","user.properties","product.properties","order.properties"}) public class ApolloApplication { public static void main(String[] args) { SpringApplication.run(ApolloApplication.class, args); } }
@EnableApolloConfig不一定要加在啟動類上,加在被spring管理的類上即可
d、在需刷新的字段上配置@Value注解,形如
@Value("${hello}") private String hello;
通過以上三步就可以實現普通字段的動態刷新
2.bean使用@ConfigurationProperties動態刷新
bean使用@ConfigurationProperties注解目前還不支持自動刷新,得編寫一定的代碼實現刷新。目前官方提供2種刷新方案
a、基于RefreshScope實現刷新
1、pom.xml要額外引入
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-context</artifactId> <version>2.0.3.RELEASE</version> </dependency>
2、bean上使用@RefreshScope注解
@Component @ConfigurationProperties(prefix = "product") @Data @AllArgsConstructor @NoArgsConstructor @Builder @RefreshScope public class Product { private Long id; private String productName; private BigDecimal price; }
3、利用RefreshScope搭配@ApolloConfigChangeListener監聽實現bean的動態刷新,其代碼實現如下
@ApolloConfigChangeListener(value="product.properties",interestedKeyPrefixes = {"product."}) private void refresh(ConfigChangeEvent changeEvent){ refreshScope.refresh("product"); PrintChangeKeyUtils.printChange(changeEvent); }
b、基于EnvironmentChangeEvent實現刷新
利用spring的事件驅動配合@ApolloConfigChangeListener監聽實現bean的動態刷新,其代碼如下
@Component @Slf4j public class UserPropertiesRefresh implements ApplicationContextAware { private ApplicationContext applicationContext; @ApolloConfigChangeListener(value="user.properties",interestedKeyPrefixes = {"user."}) private void refresh(ConfigChangeEvent changeEvent){ applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys())); PrintChangeKeyUtils.printChange(changeEvent); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
c、當bean上有@ConditionalOnProperty如何實現刷新
當bean上有@ConditionalOnProperty注解時,上述的兩種方案可以說失效了,因為@ConditionalOnProperty是一個條件注解,當不滿足條件注解時,bean是沒法注冊到spring容器中的。如果我們要實現此種情況的下的動態刷新,我們就得自己手動注冊或者銷毀bean了。其實現流程如下
1、當滿足條件注解時,則手動創建bean,然后配合@ApolloConfigChangeListener監聽該bean的屬性變化。當該bean屬性有變化時,手動把屬性注入bean。同時刷新依賴該bean的其他bean
2、當不滿足條件注解時,則手動從spring容器中移除bean,同時刷新依賴該bean的其他bean
其刷新核心代碼如下
public class OrderPropertiesRefresh implements ApplicationContextAware { private ApplicationContext applicationContext; @ApolloConfig(value = "order.properties") private Config config; @ApolloConfigChangeListener(value="order.properties",interestedKeyPrefixes = {"order."},interestedKeys = {"model.isShowOrder"}) private void refresh(ConfigChangeEvent changeEvent){ for (String basePackage : listBasePackages()) { Set<Class> conditionalClasses = ClassScannerUtils.scan(basePackage, ConditionalOnProperty.class); if(!CollectionUtils.isEmpty(conditionalClasses)){ for (Class conditionalClass : conditionalClasses) { ConditionalOnProperty conditionalOnProperty = (ConditionalOnProperty) conditionalClass.getAnnotation(ConditionalOnProperty.class); String[] conditionalOnPropertyKeys = conditionalOnProperty.name(); String beanChangeCondition = this.getChangeKey(changeEvent,conditionalOnPropertyKeys); String conditionalOnPropertyValue = conditionalOnProperty.havingValue(); boolean isChangeBean = this.changeBean(conditionalClass, beanChangeCondition, conditionalOnPropertyValue); if(!isChangeBean){ // 更新相應的bean的屬性值,主要是存在@ConfigurationProperties注解的bean applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys())); } } } } PrintChangeKeyUtils.printChange(changeEvent); printAllBeans(); } /** * 根據條件對bean進行注冊或者移除 * @param conditionalClass * @param beanChangeCondition bean發生改變的條件 * @param conditionalOnPropertyValue */ private boolean changeBean(Class conditionalClass, String beanChangeCondition, String conditionalOnPropertyValue) { boolean isNeedRegisterBeanIfKeyChange = this.isNeedRegisterBeanIfKeyChange(beanChangeCondition,conditionalOnPropertyValue); boolean isNeedRemoveBeanIfKeyChange = this.isNeedRemoveBeanIfKeyChange(beanChangeCondition,conditionalOnPropertyValue); String beanName = StringUtils.uncapitalize(conditionalClass.getSimpleName()); if(isNeedRegisterBeanIfKeyChange){ boolean isAlreadyRegisterBean = this.isExistBean(beanName); if(!isAlreadyRegisterBean){ this.registerBean(beanName,conditionalClass); return true; } }else if(isNeedRemoveBeanIfKeyChange){ this.unregisterBean(beanName); return true; } return false; } /** * bean注冊 * @param beanName * @param beanClass */ public void registerBean(String beanName,Class beanClass) { log.info("registerBean->beanName:{},beanClass:{}",beanName,beanClass); BeanDefinitionBuilder beanDefinitionBurinilder = BeanDefinitionBuilder.genericBeanDefinition(beanClass); BeanDefinition beanDefinition = beanDefinitionBurinilder.getBeanDefinition(); setBeanField(beanClass, beanDefinition); getBeanDefinitionRegistry().registerBeanDefinition(beanName,beanDefinition); } /** * 設置bean字段值 * @param beanClass * @param beanDefinition */ private void setBeanField(Class beanClass, BeanDefinition beanDefinition) { ConfigurationProperties configurationProperties = (ConfigurationProperties) beanClass.getAnnotation(ConfigurationProperties.class); if(ObjectUtils.isNotEmpty(configurationProperties)){ String prefix = configurationProperties.prefix(); for (String propertyName : config.getPropertyNames()) { String fieldPrefix = prefix + "."; if(propertyName.startsWith(fieldPrefix)){ String fieldName = propertyName.substring(fieldPrefix.length()); String fieldVal = config.getProperty(propertyName,null); log.info("setBeanField-->fieldName:{},fieldVal:{}",fieldName,fieldVal); beanDefinition.getPropertyValues().add(fieldName,fieldVal); } } } } /** * bean移除 * @param beanName */ public void unregisterBean(String beanName){ log.info("unregisterBean->beanName:{}",beanName); getBeanDefinitionRegistry().removeBeanDefinition(beanName); } public <T> T getBean(String name) { return (T) applicationContext.getBean(name); } public <T> T getBean(Class<T> clz) { return (T) applicationContext.getBean(clz); } public boolean isExistBean(String beanName){ return applicationContext.containsBean(beanName); } public boolean isExistBean(Class clz){ try { Object bean = applicationContext.getBean(clz); return true; } catch (BeansException e) { // log.error(e.getMessage(),e); } return false; } private boolean isNeedRegisterBeanIfKeyChange(String changeKey,String conditionalOnPropertyValue){ if(StringUtils.isEmpty(changeKey)){ return false; } String apolloConfigValue = config.getProperty(changeKey,null); return conditionalOnPropertyValue.equals(apolloConfigValue); } private boolean isNeedRemoveBeanIfKeyChange(String changeKey,String conditionalOnPropertyValue){ if(!StringUtils.isEmpty(changeKey)){ String apolloConfigValue = config.getProperty(changeKey,null); return !conditionalOnPropertyValue.equals(apolloConfigValue); } return false; } private boolean isChangeKey(ConfigChangeEvent changeEvent,String conditionalOnPropertyKey){ Set<String> changeKeys = changeEvent.changedKeys(); if(!CollectionUtils.isEmpty(changeKeys) && changeKeys.contains(conditionalOnPropertyKey)){ return true; } return false; } private String getChangeKey(ConfigChangeEvent changeEvent, String[] conditionalOnPropertyKeys){ if(ArrayUtils.isEmpty(conditionalOnPropertyKeys)){ return null; } String changeKey = null; for (String conditionalOnPropertyKey : conditionalOnPropertyKeys) { if(isChangeKey(changeEvent,conditionalOnPropertyKey)){ changeKey = conditionalOnPropertyKey; break; } } return changeKey; } private BeanDefinitionRegistry getBeanDefinitionRegistry(){ ConfigurableApplicationContext configurableContext = (ConfigurableApplicationContext) applicationContext; BeanDefinitionRegistry beanDefinitionRegistry = (DefaultListableBeanFactory) configurableContext.getBeanFactory(); return beanDefinitionRegistry; } private List<String> listBasePackages(){ ConfigurableApplicationContext configurableContext = (ConfigurableApplicationContext) applicationContext; return AutoConfigurationPackages.get(configurableContext.getBeanFactory()); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } public void printAllBeans() { String[] beans = applicationContext.getBeanDefinitionNames(); Arrays.sort(beans); for (String beanName : beans) { Class<?> beanType = applicationContext.getType(beanName); System.out.println(beanType); } } }
如果條件注解的值也是配置在apollo上,可能會出現依賴條件注解的bean的其他bean,在項目拉取apollo配置時,就已經注入spring容器中,此時就算條件注解滿足條件,則引用該條件注解bean的其他bean,也會拿不到條件注解bean。此時有2種方法解決,一種是在依賴條件注解bean的其他bean注入之前,先手動注冊條件注解bean到spring容器中,其核心代碼如下
@Component @Slf4j public class RefreshBeanFactory implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { Config config = ConfigService.getConfig("order.properties"); List<String> basePackages = AutoConfigurationPackages.get(configurableListableBeanFactory); for (String basePackage : basePackages) { Set<Class> conditionalClasses = ClassScannerUtils.scan(basePackage, ConditionalOnProperty.class); if(!CollectionUtils.isEmpty(conditionalClasses)){ for (Class conditionalClass : conditionalClasses) { ConditionalOnProperty conditionalOnProperty = (ConditionalOnProperty) conditionalClass.getAnnotation(ConditionalOnProperty.class); String[] conditionalOnPropertyKeys = conditionalOnProperty.name(); String beanConditionKey = this.getConditionalOnPropertyKey(config,conditionalOnPropertyKeys); String conditionalOnPropertyValue = conditionalOnProperty.havingValue(); this.registerBeanIfMatchCondition((DefaultListableBeanFactory)configurableListableBeanFactory,config,conditionalClass,beanConditionKey,conditionalOnPropertyValue); } } } } private void registerBeanIfMatchCondition(DefaultListableBeanFactory beanFactory,Config config,Class conditionalClass, String beanConditionKey, String conditionalOnPropertyValue) { boolean isNeedRegisterBean = this.isNeedRegisterBean(config,beanConditionKey,conditionalOnPropertyValue); String beanName = StringUtils.uncapitalize(conditionalClass.getSimpleName()); if(isNeedRegisterBean){ this.registerBean(config,beanFactory,beanName,conditionalClass); } } public void registerBean(Config config,DefaultListableBeanFactory beanFactory, String beanName, Class beanClass) { log.info("registerBean->beanName:{},beanClass:{}",beanName,beanClass); BeanDefinitionBuilder beanDefinitionBurinilder = BeanDefinitionBuilder.genericBeanDefinition(beanClass); BeanDefinition beanDefinition = beanDefinitionBurinilder.getBeanDefinition(); setBeanField(config,beanClass, beanDefinition); beanFactory.registerBeanDefinition(beanName,beanDefinition); } private void setBeanField(Config config,Class beanClass, BeanDefinition beanDefinition) { ConfigurationProperties configurationProperties = (ConfigurationProperties) beanClass.getAnnotation(ConfigurationProperties.class); if(ObjectUtils.isNotEmpty(configurationProperties)){ String prefix = configurationProperties.prefix(); for (String propertyName : config.getPropertyNames()) { String fieldPrefix = prefix + "."; if(propertyName.startsWith(fieldPrefix)){ String fieldName = propertyName.substring(fieldPrefix.length()); String fieldVal = config.getProperty(propertyName,null); log.info("setBeanField-->fieldName:{},fieldVal:{}",fieldName,fieldVal); beanDefinition.getPropertyValues().add(fieldName,fieldVal); } } } } public boolean isNeedRegisterBean(Config config,String beanConditionKey,String conditionalOnPropertyValue){ if(StringUtils.isEmpty(beanConditionKey)){ return false; } String apolloConfigValue = config.getProperty(beanConditionKey,null); return conditionalOnPropertyValue.equals(apolloConfigValue); } private String getConditionalOnPropertyKey(Config config, String[] conditionalOnPropertyKeys){ if(ArrayUtils.isEmpty(conditionalOnPropertyKeys)){ return null; } String changeKey = null; for (String conditionalOnPropertyKey : conditionalOnPropertyKeys) { if(isConditionalOnPropertyKey(config,conditionalOnPropertyKey)){ changeKey = conditionalOnPropertyKey; break; } } return changeKey; } private boolean isConditionalOnPropertyKey(Config config,String conditionalOnPropertyKey){ Set<String> propertyNames = config.getPropertyNames(); if(!CollectionUtils.isEmpty(propertyNames) && propertyNames.contains(conditionalOnPropertyKey)){ return true; } return false; } }
其次利用懶加載的思想,在使用條件注解bean時,使用形如下方法
Order order = (Order) SpringContextUtils.getBean("order");
看完這篇關于apollo與springboot如何集成實現動態刷新配置的文章,如果覺得文章內容寫得不錯的話,可以把它分享出去給更多人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。