apollo與springboot集成實現(xiàn)動態(tài)刷新配置的教程詳解
分布式apollo簡介
Apollo(阿波羅)是攜程框架部門研發(fā)的開源配置管理中心,能夠集中化管理應(yīng)用不同環(huán)境、不同集群的配置,配置修改后能夠?qū)崟r推送到應(yīng)用端,并且具備規(guī)范的權(quán)限、流程治理等特性。
本文主要介紹如何使用apollo與springboot實現(xiàn)動態(tài)刷新配置,如果之前不了解apollo可以查看如下文檔
https://github.com/ctripcorp/apollo
學(xué)習(xí)了解一下apollo,再來查看本文
正文
apollo與spring實現(xiàn)動態(tài)刷新配置本文主要演示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配置,配置內(nèi)容如下
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;
通過以上三步就可以實現(xiàn)普通字段的動態(tài)刷新
2.bean使用@ConfigurationProperties動態(tài)刷新
bean使用@ConfigurationProperties注解目前還不支持自動刷新,得編寫一定的代碼實現(xiàn)刷新。目前官方提供2種刷新方案
- 基于RefreshScope實現(xiàn)刷新
- 基于EnvironmentChangeEvent實現(xiàn)刷新
- 本文再提供一種,當(dāng)bean上如果使用了@ConditionalOnProperty如何實現(xiàn)刷新
a、基于RefreshScope實現(xiàn)刷新
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監(jiān)聽實現(xiàn)bean的動態(tài)刷新,其代碼實現(xiàn)如下
@ApolloConfigChangeListener(value="product.properties",interestedKeyPrefixes = {"product."})
private void refresh(ConfigChangeEvent changeEvent){
refreshScope.refresh("product");
PrintChangeKeyUtils.printChange(changeEvent);
}
b、基于EnvironmentChangeEvent實現(xiàn)刷新
利用spring的事件驅(qū)動配合@ApolloConfigChangeListener監(jiān)聽實現(xiàn)bean的動態(tài)刷新,其代碼如下
@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、當(dāng)bean上有@ConditionalOnProperty如何實現(xiàn)刷新
當(dāng)bean上有@ConditionalOnProperty注解時,上述的兩種方案可以說失效了,因為@ConditionalOnProperty是一個條件注解,當(dāng)不滿足條件注解時,bean是沒法注冊到spring容器中的。如果我們要實現(xiàn)此種情況的下的動態(tài)刷新,我們就得自己手動注冊或者銷毀bean了。其實現(xiàn)流程如下
1、當(dāng)滿足條件注解時,則手動創(chuàng)建bean,然后配合@ApolloConfigChangeListener監(jiān)聽該bean的屬性變化。當(dāng)該bean屬性有變化時,手動把屬性注入bean。同時刷新依賴該bean的其他bean
2、當(dāng)不滿足條件注解時,則手動從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){
// 更新相應(yīng)的bean的屬性值,主要是存在@ConfigurationProperties注解的bean
applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
}
}
}
}
PrintChangeKeyUtils.printChange(changeEvent);
printAllBeans();
}
/**
* 根據(jù)條件對bean進(jìn)行注冊或者移除
* @param conditionalClass
* @param beanChangeCondition bean發(fā)生改變的條件
* @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);
}
/**
* 設(shè)置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上,可能會出現(xiàn)依賴條件注解的bean的其他bean,在項目拉取apollo配置時,就已經(jīng)注入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");
總結(jié)
本文主要介紹了常用的動態(tài)刷新,但本文的代碼示例實現(xiàn)的功能不局限于此,本文的代碼還實現(xiàn)如何通過自定義注解與apollo整合來實現(xiàn)一些業(yè)務(wù)操作,同時也實現(xiàn)了基于hystrix注解與apollo整合,實現(xiàn)基于線程隔離的動態(tài)熔斷,感興趣的朋友可以復(fù)制文末鏈接到瀏覽器,進(jìn)行查看
apollo基本上是能滿足我們?nèi)粘5臉I(yè)務(wù)開發(fā)要求,但是對于一些需求,比如動態(tài)刷新線上數(shù)據(jù)庫資源啥,我們還是得做一定的量的改造,好在攜程也提供了apollo-use-cases,在里面可以找到常用的使用場景以及示例代碼,其鏈接如下
https://github.com/ctripcorp/apollo-use-cases
感興趣的朋友,可以查看下。
demo鏈接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-apollo
到此這篇關(guān)于apollo與springboot集成實現(xiàn)動態(tài)刷新配置的文章就介紹到這了,更多相關(guān)apollo與springboot集成動態(tài)刷新配置內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中String字符串常量池和intern方法源碼分析
在之前的文章中,小編給大家介紹了String字符串的不可變性及其實現(xiàn)原理,其中給大家提到了字符串常量池的概念,那么什么是常量池,String字符串與常量池有什么關(guān)系,本文給大家嘮嘮字符串常量池及String#intern()方法的作用,需要的朋友可以參考下2023-05-05
MyBatis入門學(xué)習(xí)教程(一)-MyBatis快速入門
MyBatis是一個支持普通SQL查詢,存儲過程和高級映射的優(yōu)秀持久層框架,這篇文章主要給大家分享MyBatis入門學(xué)習(xí)教程(一)-MyBatis快速入門,需要的朋友可以參考下2015-08-08
Java發(fā)送http請求的示例(get與post方法請求)
這篇文章主要介紹了Java發(fā)送http請求的示例(get與post方法請求),幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2021-01-01
如何使用@AllArgsConstructor和final 代替 @Autowired
這篇文章主要介紹了使用@AllArgsConstructor和final 代替 @Autowired方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09

