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: :8080,: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种刷新方案

基于RefreshScope实现刷新

基于EnvironmentChangeEvent实现刷新

本文再提供一种,当bean上如果使用了@ConditionalOnProperty如何实现刷新

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); } } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsszzj.html