apollo与springboot集成实现动态刷新配置的教程详解

网友投稿 3588 2023-05-28

apollo与springboot集成实现动态刷新配置的教程详解

apollo与springboot集成实现动态刷新配置的教程详解

分布式apollo简介

Apollo(阿波罗)是携程框架部门研发的开源配置管理中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性。

本文主要介绍如何使用apollo与springboot实现动态刷新配置,如果之前不了解apollo可以查看如下文档

https://github.com/ctripcorp/apollo

学习了解一下apollo,再来查看本文

正文

apollo与spring实现动态刷新配置本文主要演示2种刷新,一种基于普通字段刷新、一种基于bean上使用了@ConfigurationProperties刷新

1、普通字段刷新

a、pom.xml配置

com.ctrip.framework.apollo

apollo-client

1.6.0

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

基于RefreshScope实现刷新

基于EnvironmentChangeEvent实现刷新

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

a、基于RefreshScope实现刷新

1、pom.xml要额外引入

org.springframework.cloud

spring-cloud-context

2.0.3.RELEASE

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 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 getBean(String name) {

return (T) applicationContext.getBean(name);

}

public T getBean(Class 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 changeKeys = changeEvent.changedKeys();

if(!CollectionUtils.isEmpty(changeKeys) && changeKeys.contains(conditionalOnPropertyKey)){

rfzfAXeturn 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 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配置时,http://就已经注入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 basePackages = AutoConfigurationPackages.get(configurableListableBeanFactory);

for (String basePackage : basePackages) {

Set 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 propertyNames = config.getPropertyNames();

if(!CollectionUtils.isEmpty(propertyNames) && propertyNames.contains(conditionalOnPropertyKey)){

return true;

}

return false;

}

}

其次利用懒加载的思想,在使用条件注解bean时,使用形如下方法

Order order = (Order)

SpringContextUtils.getBean("order");

总结

本文主要介绍了常用的动态刷新,但本文的代码示例实现的功能不局限于此,本文的代码还实现如何通过自定义注解与apollo整合来实现一些业务操作,同时也实现了基于hystrix注解与apollo整合,实现基于线程隔离的动态熔断,感兴趣的朋友可以复制文末链接到浏览器,进行查看

apollo基本上是能满足我们日常的业务开发要求,但是对于一些需求,比如动态刷新线上数据库资源啥,我们还是得做一定的量的改造,好在携程也提供了apollo-use-cases,在里面可以找到常用的使用场景以及示例代码,其链接如下

https://github.com/ctripcorp/apollo-use-cases

感兴趣的朋友,可以查看下。

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-apollo

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:IDEA下Maven的pom文件导入依赖出现Auto build completed with errors的问题
下一篇:SpringBoot整合Flyway的方法(数据库版本迁移工具)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~