Введение в распределенный Apollo
Apollo — это центр управления конфигурацией с открытым исходным кодом, разработанный отделом инфраструктуры Ctrip.Он может централизованно управлять конфигурацией различных сред и кластеров приложений.После изменения конфигурации его можно передать на сторону приложения в режиме реального времени, и он стандартизирован разрешения, управление процессами и другие функции.
В этой статье в основном рассказывается, как использовать apollo и springboot для достижения конфигурации динамического обновления.Если вы раньше не знали apollo, вы можете проверить следующие документы
Узнайте об Аполлоне, а затем ознакомьтесь с этой статьей.
текст
apollo и spring для достижения конфигурации динамического обновления. В этой статье в основном демонстрируются два вида обновления: один основан на обновлении общего поля, а другой основан на bean-компоненте, использующем @ConfigurationProperties для обновления.
1. Общее обновление поля
а, конфигурация pom.xml
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.6.0</version>
</dependency>
б. 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
в) Добавьте аннотацию @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==
г. Настройте аннотацию @Value для поля, которое нужно обновить, как в
@Value("${hello}")
private String hello;
С помощью трех вышеуказанных шагов можно реализовать динамическое обновление общих полей.
2. Компонент динамически обновляется с помощью @ConfigurationProperties.
Bean-компоненты с аннотацией @ConfigurationProperties в настоящее время не поддерживают автоматическое обновление, и для обновления необходимо написать определенный код. В настоящее время доступны 2 официальные схемы обновления.
- Обновление на основе RefreshScope
- Обновление на основе EnvironmentChangeEvent
- В этой статье представлен еще один способ обновления, когда @ConditionalOnProperty используется в bean-компоненте.
А. Обновление на основе RefreshScope
1. pom.xml нужно вводить дополнительно
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
2. Используйте аннотацию @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 для мониторинга динамического обновления бинов Код реализован следующим образом
@ApolloConfigChangeListener(value="product.properties",interestedKeyPrefixes = {"product."})
private void refresh(ConfigChangeEvent changeEvent){
refreshScope.refresh("product");
PrintChangeKeyUtils.printChange(changeEvent);
}
б. Обновление на основе 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. Если мы хотим добиться динамического обновления в этом случае, мы должны вручную зарегистрировать или уничтожить бины самостоятельно. Процесс его реализации выглядит следующим образом
1. Когда условная аннотация выполняется, bean-компонент создается вручную, а затем взаимодействует с @ApolloConfigChangeListener для отслеживания изменений свойств bean-компонента. При изменении свойства bean-компонента вручную введите это свойство в bean-компонент. Также обновите другие bean-компоненты, которые зависят от этого bean-компонента.
2. Когда условная аннотация не выполняется, вручную удалите компонент из контейнера Spring и обновите другие компоненты, которые зависят от компонента.
Его основной код обновления выглядит следующим образом
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.В это время, даже если условная аннотация удовлетворяет условию, на условие ссылаются. Другие компоненты аннотированного компонента не смогут получить условно аннотированный компонент. На данный момент есть два способа решить эту проблему.Во-первых, вручную зарегистрировать компонент условной аннотации в контейнере 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 может удовлетворить наши повседневные потребности в развитии бизнеса, но для некоторых нужд, таких как динамическое обновление ресурсов онлайн-базы данных, нам все равно придется выполнить определенную трансформацию.К счастью, Ctrip также предоставляет варианты использования apollo.Распространенные сценарии использования и примеры коды можно найти в нем, а ссылки такие
Заинтересованные друзья могут посмотреть.