携程Apollo client Spring整合启动过程源码追踪

image.png

0、xml配置命名空间规则

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:task="http://www.springframework.org/schema/task"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:apollo="http://www.ctrip.com/schema/apollo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans     
        http://www.springframework.org/schema/beans/spring-beans.xsd    
        http://www.springframework.org/schema/context     
        http://www.springframework.org/schema/context/spring-context.xsd    
        http://www.springframework.org/schema/tx     
        http://www.springframework.org/schema/tx/spring-tx.xsd    
        http://www.springframework.org/schema/aop     
        http://www.springframework.org/schema/aop/spring-aop.xsd   
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/task 
        http://www.springframework.org/schema/task/spring-task.xsd    
        http://www.ctrip.com/schema/apollo 
        http://www.ctrip.com/schema/apollo.xsd">

spring xml解析beans时会找到对应spring.handlers 拓展NamespaceHandler
http://www.ctrip.com/schema/apollo=com.ctrip.framework.apollo.spring.config.NamespaceHandler

1、根据xml中的配置,spring解析道apollo命名空间后调用NamespaceHandler 重写的init方法

<apollo:config/>
/**
 * @author Jason Song(song_s@ctrip.com)
 */
public class NamespaceHandler extends NamespaceHandlerSupport {
  private static final Splitter NAMESPACE_SPLITTER = Splitter.on(",").omitEmptyStrings().trimResults();

  @Override
  public void init() {
    registerBeanDefinitionParser("config", new BeanParser());
  }

  static class BeanParser extends AbstractSingleBeanDefinitionParser {
    @Override
    protected Class<?> getBeanClass(Element element) {
      return ConfigPropertySourcesProcessor.class;
    }

    @Override
    protected boolean shouldGenerateId() {
      return true;
    }

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
      String namespaces = element.getAttribute("namespaces");
      //default to application
      if (Strings.isNullOrEmpty(namespaces)) {
        namespaces = ConfigConsts.NAMESPACE_APPLICATION;
      }

      int order = Ordered.LOWEST_PRECEDENCE;
      String orderAttribute = element.getAttribute("order");

      if (!Strings.isNullOrEmpty(orderAttribute)) {
        try {
          order = Integer.parseInt(orderAttribute);
        } catch (Throwable ex) {
          throw new IllegalArgumentException(
              String.format("Invalid order: %s for namespaces: %s", orderAttribute, namespaces));
        }
      }
//加入到静态变量中存储起来     
PropertySourcesProcessor.addNamespaces(NAMESPACE_SPLITTER.splitToList(namespaces), order);
    }
  }
}

2、handler 记载命名空间到PropertySourcesProcessor静态变量中,存储的内容就是在阿波罗管理后台定义的 application等命名空间。
3、PropertySourcesProcessor 作为beanfacotry postprocessor postProcessBeanFactory方法被调用。
4、Apollo在Spring ApplicationContext Enviroment PropertySources 存储的key = “ApolloPropertySources”,判断是否初始化过这个内容。
5、创建一个 CompositePropertySource 自定义配置源,名叫“ApolloPropertySources”。

/**
 * Apollo Property Sources processor for Spring Annotation Based Application
 *
 * @author Jason Song(song_s@ctrip.com)
 */
public class PropertySourcesProcessor implements BeanFactoryPostProcessor, EnvironmentAware, PriorityOrdered {
  private static final String APOLLO_PROPERTY_SOURCE_NAME = "ApolloPropertySources";
  private static final Multimap<Integer, String> NAMESPACE_NAMES = HashMultimap.create();

  private ConfigurableEnvironment environment;

  public static boolean addNamespaces(Collection<String> namespaces, int order) {
    return NAMESPACE_NAMES.putAll(order, namespaces);
  }

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    initializePropertySources();
  }

  protected void initializePropertySources() {
    //判断environment中是不是有叫做ApolloPropertySources的配置源
    if (environment.getPropertySources().contains(APOLLO_PROPERTY_SOURCE_NAME)) {
      //already initialized
      return;
    }
    CompositePropertySource composite = new CompositePropertySource(APOLLO_PROPERTY_SOURCE_NAME);

    //sort by order asc
    ImmutableSortedSet<Integer> orders = ImmutableSortedSet.copyOf(NAMESPACE_NAMES.keySet());
    Iterator<Integer> iterator = orders.iterator();

    while (iterator.hasNext()) {
      int order = iterator.next();
      for (String namespace : NAMESPACE_NAMES.get(order)) {
        //这里会获取对应命名空间“application”对应的Config实现类实例
        Config config = ConfigService.getConfig(namespace);
        //多个namespace放到组合配置源中
        composite.addPropertySource(new ConfigPropertySource(namespace, config));
      }
    }
    //组合数据源放到environment数据源池子中
    environment.getPropertySources().addFirst(composite);
  }

  @Override
  public void setEnvironment(Environment environment) {
    //it is safe enough to cast as all known environment is derived from ConfigurableEnvironment
    this.environment = (ConfigurableEnvironment) environment;
  }

  //only for test
   private static void reset() {
    NAMESPACE_NAMES.clear();
  }

  @Override
  public int getOrder() {
    //make it as early as possible
    return Ordered.HIGHEST_PRECEDENCE;
  }
}

6、遍历命名空间去获取对应的配置,这时候刚启动这些配置是空的,脱离spring new了一个ConfigService 开启管理。


/**
 * Entry point for client config use
 *
 * @author Jason Song(song_s@ctrip.com)
 */
public class ConfigService {
  //new 一个ConfigService 配置服务类
  private static final ConfigService s_instance = new ConfigService();

  private PlexusContainer m_container;
  private volatile ConfigManager m_configManager;
  private volatile ConfigRegistry m_configRegistry;

  private ConfigService() {
    m_container = ContainerLoader.getDefaultContainer();
  }
  //获取或创建一个管理器 下面单例多重判断,以及m_configManager 这只为volatile!
  private ConfigManager getManager() {
    if (m_configManager == null) {
      synchronized (this) {
        if (m_configManager == null) {
          try {
            m_configManager = m_container.lookup(ConfigManager.class);
          } catch (ComponentLookupException ex) {
            ApolloConfigException exception = new ApolloConfigException("Unable to load ConfigManager!", ex);
            Tracer.logError(exception);
            throw exception;
          }
        }
      }
    }

    return m_configManager;
  }
  /**
   * Get Application's config instance.
   *
   * @return config instance
   */
  public static Config getAppConfig() {
    return getConfig(ConfigConsts.NAMESPACE_APPLICATION);
  }

  /**
   * Get the config instance for the namespace.
   *
   * @param namespace the namespace of the config
   * @return config instance
   */
  public static Config getConfig(String namespace) {
    return s_instance.getManager().getConfig(namespace);
  }

7、DefaultConfigFactory 会开始创建一个namespace对应的config对象

/**
 * @author Jason Song(song_s@ctrip.com)
 */
@Named(type = ConfigFactory.class)
public class DefaultConfigFactory implements ConfigFactory {
  private static final Logger logger = LoggerFactory.getLogger(DefaultConfigFactory.class);
  @Inject
  private ConfigUtil m_configUtil;

  @Override
  public Config create(String namespace) {
    DefaultConfig defaultConfig =
        new DefaultConfig(namespace, createLocalConfigRepository(namespace));
    return defaultConfig;
  }

  @Override
  public ConfigFile createConfigFile(String namespace, ConfigFileFormat configFileFormat) {
    ConfigRepository configRepository = createLocalConfigRepository(namespace);
    switch (configFileFormat) {
      case Properties:
        return new PropertiesConfigFile(namespace, configRepository);
      case XML:
        return new XmlConfigFile(namespace, configRepository);
      case JSON:
        return new JsonConfigFile(namespace, configRepository);
      case YAML:
        return new YamlConfigFile(namespace, configRepository);
      case YML:
        return new YmlConfigFile(namespace, configRepository);
    }

    return null;
  }

  LocalFileConfigRepository createLocalConfigRepository(String namespace) {
    if (m_configUtil.isInLocalMode()) {
      logger.warn(
          "==== Apollo is in local mode! Won't pull configs from remote server for namespace {} ! ====",
          namespace);
      return new LocalFileConfigRepository(namespace);
    }
    //或创建一个本地文件配置仓库类,带命名空间以及生成一个远程配置仓库类-同步远程配置。
    return new LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace));
  }

  RemoteConfigRepository createRemoteConfigRepository(String namespace) {
    return new RemoteConfigRepository(namespace);
  }
}

8、获取对应namespace的Config对象时会在LocalFileConfigRepository 中传入一个RemoteConfigRepository

image.png

RemoteConfigRepository 创建时会尝试连接Apollo服务端

  /**
   * Constructor.
   *
   * @param namespace the namespace
   */
  public RemoteConfigRepository(String namespace) {
    m_namespace = namespace;
    m_configCache = new AtomicReference<>();
    m_container = ContainerLoader.getDefaultContainer();
    try {
      m_configUtil = m_container.lookup(ConfigUtil.class);
      m_httpUtil = m_container.lookup(HttpUtil.class);
      m_serviceLocator = m_container.lookup(ConfigServiceLocator.class);
      remoteConfigLongPollService = m_container.lookup(RemoteConfigLongPollService.class);
    } catch (ComponentLookupException ex) {
      Tracer.logError(ex);
      throw new ApolloConfigException("Unable to load component!", ex);
    }
    m_longPollServiceDto = new AtomicReference<>();
    m_loadConfigRateLimiter =   RateLimiter.create(m_configUtil.getLoadConfigQPS());
    //尝试同步远程
    this.trySync();
    //定时器同步刷新
    this.schedulePeriodicRefresh();
    this.scheduleLongPollingRefresh();
  }

9、本地文件配置仓库初始化,传入一个远程仓库对象

  public LocalFileConfigRepository(String namespace, ConfigRepository upstream) {
    m_namespace = namespace;
    m_container = ContainerLoader.getDefaultContainer();
    try {
      m_configUtil = m_container.lookup(ConfigUtil.class);
    } catch (ComponentLookupException ex) {
      Tracer.logError(ex);
      throw new ApolloConfigException("Unable to load component!", ex);
    }
    this.setLocalCacheDir(findLocalCacheDir(), false);
    //设置上游仓库,也就是仓库,这里面又个巧妙设计是有个监听器
    this.setUpstreamRepository(upstream);
    //同步本地
    this.trySync();
  }

10、将本地文件配置源自己作为监听器注册到上游(远程)配置仓库对象中

 @Override
  public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) {
    if (upstreamConfigRepository == null) {
      return;
    }
    //clear previous listener
    if (m_upstream != null) {
      m_upstream.removeChangeListener(this);
    }
    m_upstream = upstreamConfigRepository;
    trySyncFromUpstream();
    upstreamConfigRepository.addChangeListener(this);
  }
  //远程同步到变更是处罚本地文件更新
  @Override
  public void onRepositoryChange(String namespace, Properties newProperties) {
    if (newProperties.equals(m_fileProperties)) {
      return;
    }
    Properties newFileProperties = new Properties();
    newFileProperties.putAll(newProperties);
    //更新当前本地文件,更新文件会被定时加载到本地对象变量中
    updateFileProperties(newFileProperties);
     //监听器传播下去
    this.fireRepositoryChange(namespace, newProperties);
  }

11、至此,environment中的apollo propertysources就设置好了
12、准备好配置定时同步以及加载到environment中存储的对象,接下来就是如何在spring 装配时将配置对象和具体引用的对象关联起来;如下,ApolloAnnotationProcessor来解析,这是一个beanpostprocessor,当bean初始化前后来处理bean。下面用到了反射,直接将bean对应的带有@ApolloConfig("application")的属性设置值为对应命名空间的config配置仓库对象。


/**
 * Apollo Annotation Processor for Spring Application
 *
 * @author Jason Song(song_s@ctrip.com)
 */
public class ApolloAnnotationProcessor implements BeanPostProcessor, PriorityOrdered {
  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    Class clazz = bean.getClass();
    processFields(bean, clazz.getDeclaredFields());
    processMethods(bean, clazz.getDeclaredMethods());
    return bean;
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    return bean;
  }

  private void processFields(Object bean, Field[] declaredFields) {
    for (Field field : declaredFields) {
      ApolloConfig annotation = AnnotationUtils.getAnnotation(field, ApolloConfig.class);
      if (annotation == null) {
        continue;
      }

      Preconditions.checkArgument(Config.class.isAssignableFrom(field.getType()),
          "Invalid type: %s for field: %s, should be Config", field.getType(), field);
      //重点在这里
      String namespace = annotation.value();
      Config config = ConfigService.getConfig(namespace);

      ReflectionUtils.makeAccessible(field);
      ReflectionUtils.setField(field, bean, config);
    }
  }

  private void processMethods(final Object bean, Method[] declaredMethods) {
    for (final Method method : declaredMethods) {
      ApolloConfigChangeListener annotation = AnnotationUtils.findAnnotation(method, ApolloConfigChangeListener.class);
      if (annotation == null) {
        continue;
      }

      Class<?>[] parameterTypes = method.getParameterTypes();
      Preconditions.checkArgument(parameterTypes.length == 1,
          "Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length, method);
      Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]),
          "Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0], method);

      ReflectionUtils.makeAccessible(method);
      String[] namespaces = annotation.value();
      for (String namespace : namespaces) {
        Config config = ConfigService.getConfig(namespace);

        config.addChangeListener(new ConfigChangeListener() {
          @Override
          public void onChange(ConfigChangeEvent changeEvent) {
            ReflectionUtils.invokeMethod(method, bean, changeEvent);
          }
        });
      }
    }
  }

  @Override
  public int getOrder() {
    //make it as late as possible
    return Ordered.LOWEST_PRECEDENCE;
  }
}

12、经过上面的初始化操作,就完成了对象装配,业务上就可以调用获取参数配置,并且由底层仓库对象去更新配置了,业务直接用就行。

Spring 容器刷新的过程

spring refresh.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容