apollo与springboot集成实现动态刷新配置
分布式apollo简介
Apollo(阿波罗)是携程框架部门研发的开源配置管理中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性。
本文主要介绍如何使用apollo与springboot实现动态刷新配置,如果之前不了解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种刷新方案
- 基于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);
}
}
}
如果条件注解的值也是配置在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整合来实现一些业务操作,同时也实现了基于hystrix注解与apollo整合,实现基于线程隔离的动态熔断,感兴趣的朋友可以复制文末链接到浏览器,进行查看
apollo基本上是能满足我们日常的业务开发要求,但是对于一些需求,比如动态刷新线上数据库资源啥,我们还是得做一定的量的改造,好在携程也提供了apollo-use-cases,在里面可以找到常用的使用场景以及示例代码,其链接如下
感兴趣的朋友,可以查看下。
demo链接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-apollo
apollo与springboot集成实现动态刷新配置的更多相关文章
- SpringBoot集成Swagger2并配置多个包路径扫描
1. 简介 随着现在主流的前后端分离模式开发越来越成熟,接口文档的编写和规范是一件非常重要的事.简单的项目来说,对应的controller在一个包路径下,因此在Swagger配置参数时只需要配置一 ...
- SpringBoot集成Mybatis(0配置注解版)
Mybatis初期使用比较麻烦,需要各种配置文件.实体类.dao层映射关联.还有一大推其它配置.当然Mybatis也发现了这种弊端,初期开发了generator可以根据表结构自动生成实体类.配置文件和 ...
- SpringBoot集成Actuator端点配置
1.说明 Actuator端点可以监控应用程序并与之交互. Spring Boot包括许多内置的端点, 比如health端点提供基本的应用程序运行状况信息, 并允许添加自定义端点. 可以控制每个单独的 ...
- 分享知识-快乐自己:SpringBoot集成热部署配置(一)
摘要: 热部署与热加载: ava热部署与Java热加载的联系和区别: 1):Java热部署与热加载的联系: 1.不重启服务器编译/部署项目 2.基于Java的类加载器实现 2):Java热部署与热加载 ...
- springboot集成freemarker属性配置(不知道是针对于某个版本,2.0后有变动)
freemarker属性配置 freemarker属性配置: spring.freemarker.allow-request-override=false # 设置是否允许HttpServletReq ...
- springboot 集成日志 yml配置
原文:https://www.cnblogs.com/bigben0123/p/7895696.html
- SpringBoot 2.X集成 jdbc自动配置原理探究
前言 Springboot对于数据访问层,不管是 SQL还是 NOSQL,Spring Boot 底层都是采用 Spring Data 的方式统一处理.Spring Data 是 Spring 家族中 ...
- springboot集成mybatis(一)
MyBatis简介 MyBatis本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation迁移到了google code,并且改名为MyB ...
- springBoot集成Redis遇到的坑(择库)源码分析为什么择库失败
提示: springboot提供了一套链接redis的api,也就是个jar包,用到的连接类叫做LettuceConnectionConfiguration,所以我们引入pom时是这样的 <de ...
随机推荐
- ES6,ES7,ES8 常用
ES6常用新特性 let && const let 命令也用于变量声明,但是作用域为局部 { let a = 10; var b = 1; } 在函数外部可以获取到b,获取不到a,因此 ...
- Crystal | 水晶方法的七大特征,你了解吗?
本文摘自敏捷开发 20世纪90年代末,Alistair Cockburn提出水晶方法论. 自2001年的敏捷宣言提出以来,以极限编程为首的一系列敏捷方法逐渐走入大众视野,其中就包括水晶方法(Cryst ...
- vue修改对象的属性值后页面不重新渲染
原文地址:vue修改对象的属性值后页面不重新渲染 最近项目在使用vue,遇到几次修改了对象的属性后,页面并不重新渲染,场景如下: HTML页面如下: [html] view plain copy &l ...
- poj1149 经典建模
http://wenku.baidu.com/view/0ad00abec77da26925c5b01c.html 以上内容均为转载 #include<queue> #include< ...
- Lightoj1356
题目链接:https://vjudge.net/problem/LightOJ-1356 题目大意: T个 test case,每个 test case 给出一个 N 个数的集合.请找出一个最大的子集 ...
- 通过Python扫描代码关键字并进行预警
近期线上出现一个bug,研发的小伙伴把测试环境的地址写死到代码中,在上线前忘记修改,导致线上发布的代码中使用了测试环境地址. 开发过程中虽然有各种规范制度,但是难免有粗心,与其责备不如通过技术手段将问 ...
- idea的生成类注释和方法注释
sttings中选择 类注释 /** * @program: ${PROJECT_NAME} * * @description: ${description} * * @author: xiaozha ...
- Collection接口和list,set子类
Collection接口常用的子接口有:List接口.Set接口List接口常用的子类有:ArrayList类.LinkedList类Set接口常用的子类有:HashSet类.LinkedHashSe ...
- Layui 改变数据表格样式覆盖
改变表格行高.layui-table-cell{ height:40px; line-height: 36px; } 改变复选框高宽和定位等等.layui-table-view .layui-form ...
- pytest常用命令参数
pytest 参数 1.参数:-s 运行过程中执行print打印函数:pytest -s,以下两个输出 上边带参数,下边不带 2.参数: --collect-only 收集将要执行的用例,但不会执行用 ...