How does Java Apollo implement configuration updates

  • 2021-09-05 00:08:44
  • OfStack

This document focuses on how the corresponding Java object is updated after the next configuration modification, and does not pay attention to the overall configuration change process

All code comes from the apollo-client project

Update process

After the Apollo console is modified and published, the corresponding client terminal pulls the update and calls the com. ctrip. framework. apollo. spring. property. AutoUpdateConfigChangeListener # onChange method

When calling onChange, you will receive the corresponding modified configuration information ConfigChangeEvent, which contains the modified key and value, and the modification process is as follows:

The corresponding Spring Bean information associated with the key is found from the springValueRegistry according to the modified configuration of the key, and if it is not found, it is not processed The corresponding associated configuration is updated according to the found Spring Bean information

In Step 2, you will determine whether the association configuration is associated with attribute association or method. The code is as follows


public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {
  if (isField()) {
    injectField(newVal);
  } else {
    injectMethod(newVal);
  }
}

Among the above questions, there are still two questions in doubt

How to find the corresponding Spring Bean information through key How to convert the configuration value of Apollo to the recognized value of Spring

public class AutoUpdateConfigChangeListener implements ConfigChangeListener{
 private static final Logger logger = LoggerFactory.getLogger(AutoUpdateConfigChangeListener.class);

 private final boolean typeConverterHasConvertIfNecessaryWithFieldParameter;
 private final Environment environment;
 private final ConfigurableBeanFactory beanFactory;
 private final TypeConverter typeConverter;
 private final PlaceholderHelper placeholderHelper;
 private final SpringValueRegistry springValueRegistry;
 private final Gson gson;

 public AutoUpdateConfigChangeListener(Environment environment, ConfigurableListableBeanFactory beanFactory){
  this.typeConverterHasConvertIfNecessaryWithFieldParameter = testTypeConverterHasConvertIfNecessaryWithFieldParameter();
  this.beanFactory = beanFactory;
  this.typeConverter = this.beanFactory.getTypeConverter();
  this.environment = environment;
  this.placeholderHelper = SpringInjector.getInstance(PlaceholderHelper.class);
  this.springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class);
  this.gson = new Gson();
 }

 @Override
 public void onChange(ConfigChangeEvent changeEvent) {
  Set<String> keys = changeEvent.changedKeys();
  if (CollectionUtils.isEmpty(keys)) {
   return;
  }
  for (String key : keys) {
   // 1. check whether the changed key is relevant
   Collection<SpringValue> targetValues = springValueRegistry.get(beanFactory, key);
   if (targetValues == null || targetValues.isEmpty()) {
    continue;
   }

   // 2. update the value
   for (SpringValue val : targetValues) {
    updateSpringValue(val);
   }
  }
 }

 private void updateSpringValue(SpringValue springValue) {
  try {
   Object value = resolvePropertyValue(springValue);
   springValue.update(value);

   logger.info("Auto update apollo changed value successfully, new value: {}, {}", value,
     springValue);
  } catch (Throwable ex) {
   logger.error("Auto update apollo changed value failed, {}", springValue.toString(), ex);
  }
 }

 /**
  * Logic transplanted from DefaultListableBeanFactory
  * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency(org.springframework.beans.factory.config.DependencyDescriptor, java.lang.String, java.util.Set, org.springframework.beans.TypeConverter)
  */
 private Object resolvePropertyValue(SpringValue springValue) {
  // value will never be null, as @Value and @ApolloJsonValue will not allow that
  Object value = placeholderHelper
    .resolvePropertyValue(beanFactory, springValue.getBeanName(), springValue.getPlaceholder());

  if (springValue.isJson()) {
   value = parseJsonValue((String)value, springValue.getGenericType());
  } else {
   if (springValue.isField()) {
    // org.springframework.beans.TypeConverter#convertIfNecessary(java.lang.Object, java.lang.Class, java.lang.reflect.Field) is available from Spring 3.2.0+
    if (typeConverterHasConvertIfNecessaryWithFieldParameter) {
     value = this.typeConverter
       .convertIfNecessary(value, springValue.getTargetType(), springValue.getField());
    } else {
     value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType());
    }
   } else {
    value = this.typeConverter.convertIfNecessary(value, springValue.getTargetType(),
      springValue.getMethodParameter());
   }
  }

  return value;
 }

 private Object parseJsonValue(String json, Type targetType) {
  try {
   return gson.fromJson(json, targetType);
  } catch (Throwable ex) {
   logger.error("Parsing json '{}' to type {} failed!", json, targetType, ex);
   throw ex;
  }
 }

 private boolean testTypeConverterHasConvertIfNecessaryWithFieldParameter() {
  try {
   TypeConverter.class.getMethod("convertIfNecessary", Object.class, Class.class, Field.class);
  } catch (Throwable ex) {
   return false;
  }
  return true;
 }
}

How to Configure key and Spring Bean

There are two common configurations in Spring


public class ApiConfig {
 
 	// 1.  Directly in  Field  Is to inject 
  @Value("${feifei.appId}")
  protected String appId;

  protected String predUrl;

 	// 2.  Inject on the method 
  @Value("${predUrl}")
  public void setPredUrl(String predUrl) {
    this.predUrl = predUrl;
  }
}

In the Apollo code, by implementing the BeanPostProcessor Interface to detect all Spring Bean creation process, and the corresponding Spring Bean creation process will be called org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization And org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization Method.

Apollo detects the existence of attributes and methods in the Bean class during the generation of Bean @Value Note, if any, put forward key, and its processing method is in processField And processMethod Handle the possible occurrences in Field and Method respectively @Value Annotations. If there are annotations, the corresponding information is stored in SpringValue Correspondence springValueRegistry Global object, convenient in other places can be directly obtained.

In addition to the attribute through @Value Injection, can also be configured with xml, in this case through org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization 0 Method to handle the

key can be associated with the corresponding Spring Bean information by two processing methods


public class SpringValueProcessor extends ApolloProcessor implements BeanFactoryPostProcessor, BeanFactoryAware {

 private static final Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class);

 private final ConfigUtil configUtil;
 private final PlaceholderHelper placeholderHelper;
 private final SpringValueRegistry springValueRegistry;

 private BeanFactory beanFactory;
 private Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions;

 public SpringValueProcessor() {
  configUtil = ApolloInjector.getInstance(ConfigUtil.class);
  placeholderHelper = SpringInjector.getInstance(PlaceholderHelper.class);
  springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class);
  beanName2SpringValueDefinitions = LinkedListMultimap.create();
 }

 @Override
 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
   throws BeansException {
  if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled() && beanFactory instanceof BeanDefinitionRegistry) {
   beanName2SpringValueDefinitions = SpringValueDefinitionProcessor
     .getBeanName2SpringValueDefinitions((BeanDefinitionRegistry) beanFactory);
  }
 }

 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName)
   throws BeansException {
  if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
   super.postProcessBeforeInitialization(bean, beanName);
   processBeanPropertyValues(bean, beanName);
  }
  return bean;
 }


 @Override
 protected void processField(Object bean, String beanName, Field field) {
  // register @Value on field
  Value value = field.getAnnotation(Value.class);
  if (value == null) {
   return;
  }
  Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());

  if (keys.isEmpty()) {
   return;
  }

  for (String key : keys) {
   SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
   springValueRegistry.register(beanFactory, key, springValue);
   logger.debug("Monitoring {}", springValue);
  }
 }

 @Override
 protected void processMethod(Object bean, String beanName, Method method) {
  //register @Value on method
  Value value = method.getAnnotation(Value.class);
  if (value == null) {
   return;
  }
  //skip Configuration bean methods
  if (method.getAnnotation(Bean.class) != null) {
   return;
  }
  if (method.getParameterTypes().length != 1) {
   logger.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters",
     bean.getClass().getName(), method.getName(), method.getParameterTypes().length);
   return;
  }

  Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());

  if (keys.isEmpty()) {
   return;
  }

  for (String key : keys) {
   SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method, false);
   springValueRegistry.register(beanFactory, key, springValue);
   logger.info("Monitoring {}", springValue);
  }
 }


 private void processBeanPropertyValues(Object bean, String beanName) {
  Collection<SpringValueDefinition> propertySpringValues = beanName2SpringValueDefinitions
    .get(beanName);
  if (propertySpringValues == null || propertySpringValues.isEmpty()) {
   return;
  }

  for (SpringValueDefinition definition : propertySpringValues) {
   try {
    PropertyDescriptor pd = BeanUtils
      .getPropertyDescriptor(bean.getClass(), definition.getPropertyName());
    Method method = pd.getWriteMethod();
    if (method == null) {
     continue;
    }
    SpringValue springValue = new SpringValue(definition.getKey(), definition.getPlaceholder(),
      bean, beanName, method, false);
    springValueRegistry.register(beanFactory, definition.getKey(), springValue);
    logger.debug("Monitoring {}", springValue);
   } catch (Throwable ex) {
    logger.error("Failed to enable auto update feature for {}.{}", bean.getClass(),
      definition.getPropertyName());
   }
  }

  // clear
  beanName2SpringValueDefinitions.removeAll(beanName);
 }

 @Override
 public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
  this.beanFactory = beanFactory;
 }
}

The above is how Java Apollo configuration update details, more information about Java Apollo configuration update please pay attention to other related articles on this site!


Related articles: