Introduction to distributed apollo
Apollo (Apollo) is an open source configuration management center developed by Ctrip framework department. It can centrally manage the configuration of different application environments and clusters. After the configuration is modified, it can be pushed to the application end in real time, and has the characteristics of standardized authority, process governance and so on.
This article mainly introduces how to use apollo and springboot to realize dynamic refresh configuration. If you don't know about apollo before, you can check the following documents
https://github.com/ctripcorp/apollo
Learn about apollo and check out this article
text
apollo and spring implement dynamic refresh configuration. This paper mainly demonstrates two kinds of refresh, one based on common fields and the other based on bean s using @ ConfigurationProperties
1. Normal field refresh
a,pom.xml configuration
<dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-client</artifactId> <version>1.6.0</version> </dependency>
b. Client configuration AppId, Apollo Meta Server
There are several methods for this configuration. This example is directly in application YML configuration, the configuration contents are as follows
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. Add @ enableapoloconfig annotation on the startup class in the project, as shown below
@SpringBootApplication @EnableApolloConfig(value = {"application","user.properties","product.properties","order.properties"}) public class ApolloApplication { public static void main(String[] args) { SpringApplication.run(ApolloApplication.class, args); } }
@Enableaploconfig does not have to be added to the startup class, but to the class managed by spring
d. Configure @ Value annotation on the field to be refreshed, as shown in
@Value("${hello}") private String hello;
Through the above three steps, you can realize the dynamic refresh of common fields
2. Beans are refreshed dynamically using @ ConfigurationProperties
bean annotation using @ ConfigurationProperties does not support automatic refresh at present. You have to write some code to refresh. At present, two refresh schemes are officially provided
- Refresh based on RefreshScope
- Refresh based on EnvironmentChangeEvent
- This article also provides a method to implement refresh if @ ConditionalOnProperty is used on the bean
a. Refresh based on RefreshScope
1,pom.xml should be introduced additionally
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-context</artifactId> <version>2.0.3.RELEASE</version> </dependency>
2. Use @ RefreshScope annotation on bean
@Component @ConfigurationProperties(prefix = "product") @Data @AllArgsConstructor @NoArgsConstructor @Builder @RefreshScope public class Product { private Long id; private String productName; private BigDecimal price; }
3. Use RefreshScope with @ Apollo configchangelistener to monitor and realize the dynamic refresh of bean s. The code is as follows
@ApolloConfigChangeListener(value="product.properties",interestedKeyPrefixes = {"product."}) private void refresh(ConfigChangeEvent changeEvent){ refreshScope.refresh("product"); PrintChangeKeyUtils.printChange(changeEvent); }
b. Refresh based on EnvironmentChangeEvent
Use spring's event driver and @ Apollo configchangelistener to listen to realize the dynamic refresh of bean s. The code is as follows
@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. How to refresh when there is @ ConditionalOnProperty on the bean
When there is a @ ConditionalOnProperty annotation on the bean, the above two schemes are invalid, because @ ConditionalOnProperty is a conditional annotation. When the conditional annotation is not satisfied, the bean cannot be registered in the spring container. If we want to achieve dynamic refresh in this case, we have to register or destroy the beans manually. The implementation process is as follows
1. When the conditional annotation is met, create the bean manually, and then cooperate with @ Apollo configchangelistener to listen for the property changes of the bean. When the bean property changes, manually inject the property into the bean. Refresh other beans that depend on this bean at the same time
2. When the conditional annotation is not satisfied, manually remove the bean from the spring container and refresh other beans that depend on the bean
The refresh core code is as follows
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){ // Update the property value of the corresponding bean, mainly the bean with @ ConfigurationProperties annotation applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys())); } } } } PrintChangeKeyUtils.printChange(changeEvent); printAllBeans(); } /** * Register or remove bean s according to conditions * @param conditionalClass * @param beanChangeCondition bean Changed conditions * @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 register * @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); } /** * Set bean field value * @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 remove * @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); } } }
If the value of the condition annotation is also configured on apollo, there may be other beans of beans that depend on the condition annotation. When the project pulls the apollo configuration, it has been injected into the spring container. At this time, even if the condition annotation meets the condition, other beans referencing the condition annotation bean will not get the condition annotation bean. There are two ways to solve this problem. One is to manually register the conditional annotation bean into the spring container before injecting other beans that depend on the conditional annotation bean. The core code is as follows
@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; } }
Secondly, using the idea of lazy loading, when using conditional annotation bean s, the following methods are used
Order order = (Order) SpringContextUtils.getBean("order");
summary
This paper mainly introduces the commonly used dynamic refresh, but the functions realized by the code examples in this paper are not limited to this. The code in this paper also realizes how to realize some business operations through the integration of custom annotations and apollo. At the same time, it also realizes the integration of hystrix annotations and apollo to realize the dynamic fusing based on thread isolation. Interested friends can copy the text and link to the browser, View
apollo can basically meet our daily business development requirements, but for some needs, such as dynamically refreshing online database resources, we still have to make a certain amount of transformation. Fortunately, Ctrip also provides apollo use cases, in which you can find common use scenarios and sample codes, and the links are as follows
https://github.com/ctripcorp/apollo-use-cases
Interested friends can view it.
last
If you want to ask whether front-end development is difficult or not, I have to say a sentence often said in the computer field. This sentence is "difficult will not, will not be difficult". For people who are not familiar with the technology in a certain field, they have a sense of mystery because they do not understand it. The sense of mystery will make people feel very difficult, that is, "difficult will not"; When you learn this technology, you know what technology can do and what you can't do. It's just a matter of how long it takes to do it. It's not difficult, so it's "it's not difficult to do it".
I specially organize a set of front-end learning materials for beginners and share them for free, You can get it for free by poking here
We have to do a certain amount of transformation. Fortunately, Ctrip also provides Apollo use cases, in which we can find common use scenarios and sample code, and the links are as follows
https://github.com/ctripcorp/apollo-use-cases
Interested friends can view it.
last
If you want to ask whether front-end development is difficult or not, I have to say a sentence often said in the computer field. This sentence is "difficult will not, will not be difficult". For people who are not familiar with the technology in a certain field, they have a sense of mystery because they do not understand it. The sense of mystery will make people feel very difficult, that is, "difficult will not"; When you learn this technology, you know what technology can do and what you can't do. It's just a matter of how long it takes to do it. It's not difficult, so it's "it's not difficult to do it".
I specially organize a set of front-end learning materials for beginners and share them for free, You can get it for free by poking here
[external chain picture transferring... (img-ZWkBXkZP-1626932692434)]