spring cloud Config源码解析(二)

阅读更多

    前面说完了一些公共bean的配置引入,我们具体来介绍下git相关代码的实现。通过在EnvironmentRepositoryConfiguration类中,我们可以看到有两处实现git bean的地方,都是使用内部类的方式实现,分别是JGitFactoryConfig和DefaultRepositoryConfiguration,代码如下所示

@Configuration
@ConditionalOnClass(TransportConfigCallback.class)
static class JGitFactoryConfig {

    @Bean
    public MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory(
            ConfigurableEnvironment environment, ConfigServerProperties server,
            Optional jgitHttpConnectionFactory,
            Optional customTransportConfigCallback) {
        return new MultipleJGitEnvironmentRepositoryFactory(environment, server, jgitHttpConnectionFactory,
   customTransportConfigCallback);
    }
}

 

@Configuration
@ConditionalOnMissingBean(value = EnvironmentRepository.class)//, search = SearchStrategy.CURRENT)
class DefaultRepositoryConfiguration {
   @Autowired
   private ConfigurableEnvironment environment;

   @Autowired
   private ConfigServerProperties server;

   @Autowired(required = false)
   private TransportConfigCallback customTransportConfigCallback;

   @Bean
   public MultipleJGitEnvironmentRepository defaultEnvironmentRepository(
           MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory,
         MultipleJGitEnvironmentProperties environmentProperties) throws Exception {
      return gitEnvironmentRepositoryFactory.build(environmentProperties);
   }
}

 

    从上面的代码中,我们可以看到都是通过MultipleJGitEnvironmentRepositoryFactory工厂类进行加载的,由于static类优先加载,所以首先执行的是前面部分代码来初始化工厂类

  我们进入到MultipleJGitEnvironmentRepositoryFactory工厂类可以看到new MultipleJGitEnvironmentRepositoryFactory只是做了一个参数初始化工作,而真正起作用的是bulid()方法,bulid()方法的代码如下

public MultipleJGitEnvironmentRepository build(MultipleJGitEnvironmentProperties environmentProperties)
      throws Exception {
   if (connectionFactory.isPresent()) {
      HttpTransport.setConnectionFactory(connectionFactory.get());
      connectionFactory.get().addConfiguration(environmentProperties);
   }

   MultipleJGitEnvironmentRepository repository = new MultipleJGitEnvironmentRepository(environment,
         environmentProperties);
   repository.setTransportConfigCallback(customTransportConfigCallback
         .orElse(buildTransportConfigCallback(environmentProperties)));
   if (server.getDefaultLabel() != null) {
      repository.setDefaultLabel(server.getDefaultLabel());
   }
   return repository;
}

    首先判断ConfigurableHttpConnectionFactory对象是否存在,通过查看该对象类,我们可以了解到该类主要是读取配置文件中spring.cloud.config.server.git的perfix的值来组成HttpClientConnection连接信息,用于连接git服务器的基础信息,处理完HttpClientConnection信息再初始化MultipleJGitEnvironmentRepository类,一会我们在分析该类,然后给MultipleJGitEnvironmentRepository类设置回调类,回调类不存在的情况下则新增一个回调类用于与git仓库连接,分别使用SshSessionFactory和SshTransport两种方式与git仓库连接,最后判断是否有默认的标签(label,就是一个版本),有就设置。

接下来我们分析下MultipleJGitEnvironmentRepository类,我们先看下它的继承关系图


spring cloud Config源码解析(二)_第1张图片
      进入到这个类,我们首先可以看到它继承自JGitEnvironmentRepository类

public class MultipleJGitEnvironmentRepository extends JGitEnvironmentRepository

      我们再次进入到JGitEnvironmentRepository类,可以看到该类继承了AbstractScmEnvironmentRepository抽象类和实现了EnvironmentRepository, SearchPathLocator, InitializingBean这3个接口类

public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository
      implements EnvironmentRepository, SearchPathLocator, InitializingBean

       进入到AbstractScmEnvironmentRepository类可以看到它继承了AbstractScmAccessor抽象类和实现了EnvironmentRepository, SearchPathLocator, Ordered这3个接口

public abstract class AbstractScmEnvironmentRepository extends AbstractScmAccessor
      implements EnvironmentRepository, SearchPathLocator, Ordered

     再进入到AbstractScmAccessor抽象类可以看到它实现了ResourceLoaderAware接口类

public abstract class AbstractScmAccessor implements ResourceLoaderAware

     而ResourceLoaderAware接口是用于加载外部资源文件的接口,这里具体使用了DefaultResourceLoader类,这个在这里不多做介绍了,我们从上到下,从最基础的AbstractScmAccessor抽象类介绍到MultipleJGitEnvironmentRepository类的实现,这样有助于我们理解。

1.AbstractScmAccessor抽象类

    从代码中可以看到该类主要定义一些在spring.cloud.config.server出现的属性,同时可以看到在没有定义basedir属性的时候,通过createBaseDir()方法自动生成一个临时文件路径

protected File createBaseDir() {
   try {
      final Path basedir = Files.createTempDirectory("config-repo-");
      Runtime.getRuntime().addShutdownHook(new Thread() {
         @Override
         public void run() {
            try {
               FileSystemUtils.deleteRecursively(basedir);
            }
            catch (IOException e) {
               AbstractScmAccessor.this.logger.warn(
                     "Failed to delete temporary directory on exit: " + e);
            }
         }
      });
      return basedir.toFile();
   }
   catch (IOException e) {
      throw new IllegalStateException("Cannot create temp dir", e);
   }
}

     通过getSearchLocations()这个方法可以了解到主要是到创建配置文件的文件夹路径,把placeholder占位符替换为具体的配置prefix

protected String[] getSearchLocations(File dir, String application, String profile,
      String label) {
   String[] locations = this.searchPaths;
   if (locations == null || locations.length == 0) {
      locations = AbstractScmAccessorProperties.DEFAULT_LOCATIONS;
   }
   else if (locations != AbstractScmAccessorProperties.DEFAULT_LOCATIONS) {
      locations = StringUtils.concatenateStringArrays(AbstractScmAccessorProperties.DEFAULT_LOCATIONS, locations);
   }
   Collection output = new LinkedHashSet();
   for (String location : locations) {
      String[] profiles = new String[] { profile };
      if (profile != null) {
         profiles = StringUtils.commaDelimitedListToStringArray(profile);
      }
      String[] apps = new String[] { application };
      if (application != null) {
         apps = StringUtils.commaDelimitedListToStringArray(application);
      }
      for (String prof : profiles) {
         for (String app : apps) {
            String value = location;
            if (app != null) {
               value = value.replace("{application}", app);
            }
            if (prof != null) {
               value = value.replace("{profile}", prof);
            }
            if (label != null) {
               value = value.replace("{label}", label);
            }
            if (!value.endsWith("/")) {
               value = value + "/";
            }
            output.addAll(matchingDirectories(dir, value));
         }
      }
   }
   return output.toArray(new String[0]);
}

 2.AbstractScmEnvironmentRepository

    我们主要看findOne()方法

public synchronized Environment findOne(String application, String profile, String label) {
   NativeEnvironmentRepository delegate = new NativeEnvironmentRepository(getEnvironment(),
         new NativeEnvironmentProperties());
   Locations locations = getLocations(application, profile, label);
   delegate.setSearchLocations(locations.getLocations());
   Environment result = delegate.findOne(application, profile, "");
   result.setVersion(locations.getVersion());
   result.setLabel(label);
   return this.cleaner.clean(result, getWorkingDirectory().toURI().toString(),
         getUri());
}

     在这个方法的3个参数application,profile,lable分别来自客户端配置文件中定义的spring.cloud.config.namespring.cloud.config.profilespring.cloud.config.label,代码中首先定义了一个NativeEnvironmentRepository类对象,后一段代码是通过具体的getLocations()方法实现把placeholder占位符{application}{profile}{label}替换掉为具体的配置值,第4行的NativeEnvironmentRepository.findOne()方法

public Environment findOne(String config, String profile, String label) {
   SpringApplicationBuilder builder = new SpringApplicationBuilder(
         PropertyPlaceholderAutoConfiguration.class);
   ConfigurableEnvironment environment = getEnvironment(profile);
   builder.environment(environment);
   builder.web(WebApplicationType.NONE).bannerMode(Mode.OFF);
   if (!logger.isDebugEnabled()) {
      // Make the mini-application startup less verbose
      builder.logStartupInfo(false);
   }
   String[] args = getArgs(config, profile, label);
   // Explicitly set the listeners (to exclude logging listener which would change
   // log levels in the caller)
   builder.application()
         .setListeners(Arrays.asList(new ConfigFileApplicationListener()));
   ConfigurableApplicationContext context = builder.run(args);
   environment.getPropertySources().remove("profiles");
   try {
      return clean(new PassthruEnvironmentRepository(environment).findOne(config,
            profile, label));
   }
   finally {
      context.close();
   }
}

     进入到该方法,可以看到首先定义了SpringApplicationBuilder类对象builder,后一行代码获取当前的环境变量信息environment并把environment设置到builder中,设置builder在后端启动线程运行模式,在getArgs()方法中初始化一些配置项

private String[] getArgs(String application, String profile, String label) {
   List list = new ArrayList();
   String config = application;
   if (!config.startsWith("application")) {
      config = "application," + config;
   }
   list.add("--spring.config.name=" + config);
   list.add("--spring.cloud.bootstrap.enabled=false");
   list.add("--encrypt.failOnError=" + this.failOnError);
   list.add("--spring.config.location=" + StringUtils.arrayToCommaDelimitedString(
         getLocations(application, profile, label).getLocations()));
   return list.toArray(new String[0]);
}

     然后添加一个ConfigFileApplicationListener的监听类,该监听主要用于配置文件的覆盖的监听,再通过builder.run()方法启动一个新线程来连接rabbit mq,启动后再环境变量中删除propertySource属性的profile变量,最后处理环境变量中propertySources数组name的文件路径为形如file:/D:/baseConfig/publicConfig/merchant1-dev.properties这样的格式,同时取出merchant1-dev.properties文件中的键值对到propertySources属性为source的map集合中

public Environment findOne(String application, String env, String label) {
   Environment result = new Environment(application,
         StringUtils.commaDelimitedListToStringArray(env), label, null, null);
   for (org.springframework.core.env.PropertySource source : this.environment
         .getPropertySources()) {
      String name = source.getName();
      if (!this.standardSources.contains(name)
            && source instanceof MapPropertySource) {
         result.add(new PropertySource(name, getMap(source)));
      }
   }
   return result;

}

      退出NativeEnvironmentRepository.findOne()方法后回到AbstractScmEnvironmentRepository.findOne()方法中,在环境变量environment中加入label和version,最后在EnvironmentCleaner.clean()方法中处理file:/D:/baseConfig/publicConfig/merchant1-dev.properties为https://github.com/422518490/orderSystem/publicConfig/merchant1-dev.properties这种格式的路径

protected Environment clean(Environment value) {
   Environment result = new Environment(value.getName(), value.getProfiles(),
         value.getLabel(), this.version, value.getState());
   for (PropertySource source : value.getPropertySources()) {
      String name = source.getName();
      if (this.environment.getPropertySources().contains(name)) {
         continue;
      }
      name = name.replace("applicationConfig: [", "");
      name = name.replace("]", "");
      if (this.searchLocations != null) {
         boolean matches = false;
         String normal = name;
         if (normal.startsWith("file:")) {
            normal = StringUtils
                  .cleanPath(new File(normal.substring("file:".length()))
                        .getAbsolutePath());
         }
         String profile = result.getProfiles() == null ? null
               : StringUtils.arrayToCommaDelimitedString(result.getProfiles());
         for (String pattern : getLocations(result.getName(), profile,
               result.getLabel()).getLocations()) {
            if (!pattern.contains(":")) {
               pattern = "file:" + pattern;
            }
            if (pattern.startsWith("file:")) {
               pattern = StringUtils
                     .cleanPath(new File(pattern.substring("file:".length()))
                           .getAbsolutePath())
                     + "/";
            }
            if (logger.isTraceEnabled()) {
               logger.trace("Testing pattern: " + pattern
                     + " with property source: " + name);
            }
            if (normal.startsWith(pattern)
                  && !normal.substring(pattern.length()).contains("/")) {
               matches = true;
               break;
            }
         }
         if (!matches) {
            // Don't include this one: it wasn't matched by our search locations
            if (logger.isDebugEnabled()) {
               logger.debug("Not adding property source: " + name);
            }
            continue;
         }
      }
      logger.info("Adding property source: " + name);
      result.add(new PropertySource(name, source.getSource()));
   }
   return result;
}

     在此就讨论完AbstractScmEnvironmentRepository类的处理。

3.JGitEnvironmentRepository

    这个类主要定义了一些初始化配置文件以及从git上获取配置文件的方法。从构造函数可以看出,初始化的信息主要来自配置文件的prefix

public JGitEnvironmentRepository(ConfigurableEnvironment environment, JGitEnvironmentProperties properties) {
   super(environment, properties);
   this.cloneOnStart = properties.isCloneOnStart();
   this.defaultLabel = properties.getDefaultLabel();
   this.forcePull = properties.isForcePull();
   this.timeout = properties.getTimeout();
   this.deleteUntrackedBranches = properties.isDeleteUntrackedBranches();
   this.refreshRate = properties.getRefreshRate();
   this.skipSslValidation = properties.isSkipSslValidation();
}

     在getLocations()方法中,主要是定义Locations类信息以及刷新git提交的版本号,我们可以通过refresh()方法来发现是如何去git获取版本号的

public String refresh(String label) {
   Git git = null;
   try {
      git = createGitClient();
      if (shouldPull(git)) {
         FetchResult fetchStatus = fetch(git, label);
         if (deleteUntrackedBranches && fetchStatus != null) {
            deleteUntrackedLocalBranches(fetchStatus.getTrackingRefUpdates(), git);
         }
         // checkout after fetch so we can get any new branches, tags, ect.
         checkout(git, label);
         tryMerge(git, label);
      }
      else {
         // nothing to update so just checkout and merge.
         // Merge because remote branch could have been updated before
         checkout(git, label);
         tryMerge(git, label);
      }
      // always return what is currently HEAD as the version
      return git.getRepository().findRef("HEAD").getObjectId().getName();
   }
   catch (RefNotFoundException e) {
      throw new NoSuchLabelException("No such label: " + label, e);
   }
   catch (NoRemoteRepositoryException e) {
      throw new NoSuchRepositoryException("No such repository: " + getUri(), e);
   }
   catch (GitAPIException e) {
      throw new NoSuchRepositoryException(
            "Cannot clone or checkout repository: " + getUri(), e);
   }
   catch (Exception e) {
      throw new IllegalStateException("Cannot load environment", e);
   }
   finally {
      try {
         if (git != null) {
            git.close();
         }
      }
      catch (Exception e) {
         this.logger.warn("Could not close git repository", e);
      }
   }
}

     从上面的代码中可以了解到是一个从git获取配置文件的过程,它是以的是eclipse提供的git api方法,先是初始化git的信息,具体初始化信息可以去代码查看,然后在满足重新获取git上的文件信息情况下,重新拉取和覆盖文件信息,删除不需要的分支。 

     在afterPropertiesSet()方法中,它是作为在properties信息加载完成后进行的一些初始化操作,在该方法中initClonedRepository()方法在满足启动的时候prefix属性设置为true是进入该方法

private void initClonedRepository() throws GitAPIException, IOException {
   if (!getUri().startsWith(FILE_URI_PREFIX)) {
      deleteBaseDirIfExists();
      Git git = cloneToBasedir();
      if (git != null) {
         git.close();
      }
      git = openGitRepository();
      if (git != null) {
         git.close();
      }
   }

}

     从该方法可以看出主要是在初始化的时候删除已经存在的文件,以及从git上面拉取配置文件路径下的文件信息到本地重新生成文件信息,具体的逻辑代码可以到代码里面查看。

     在refresh()方法中通过new Locations()方法返回该对象,在new的过程中通过getSearchLocations()拆分形成了形如的file:/D:/baseConfig/和file:/D:/baseConfig/publicConfig/的数组String路径,这个路径是在配置bootstrap.properties中定义的。

4.MultipleJGitEnvironmentRepository

    从构造函数可以看出,当有多个配置中心的时候会通过以下代码进行分解取值

properties.getRepos().entrySet().stream()
      .map(e -> new AbstractMap.SimpleEntry<>(e.getKey(),
            new PatternMatchingJGitEnvironmentRepository(environment, e.getValue())))
      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))

     通过重写afterPropertiesSet()方法在加载完成prefix之后初始化信息。我们在来主要看一下findOne()方法

public Environment findOne(String application, String profile, String label) {
   for (PatternMatchingJGitEnvironmentRepository repository : this.repos.values()) {
      if (repository.matches(application, profile, label)) {
         for (JGitEnvironmentRepository candidate : getRepositories(repository,
               application, profile, label)) {
            try {
               if (label == null) {
                  label = candidate.getDefaultLabel();
               }
               Environment source = candidate.findOne(application, profile,
                     label);
               if (source != null) {
                  return source;
               }
            }
            catch (Exception e) {
               if (logger.isDebugEnabled()) {
                  this.logger.debug(
                        "Cannot load configuration from " + candidate.getUri()
                              + ", cause: (" + e.getClass().getSimpleName()
                              + ") " + e.getMessage(),
                        e);
               }
               continue;
            }
         }
      }
   }
   JGitEnvironmentRepository candidate = getRepository(this, application, profile,
         label);
   if (label == null) {
      label = candidate.getDefaultLabel();
   }
   if (candidate == this) {
      return super.findOne(application, profile, label);
   }
   return candidate.findOne(application, profile, label);
}

     在方法中首先通过获取Map中的配置中心信息来循环进行获取配置信息,前提是已经赋值了Map集合信息,在for循环中经过getRepositories()方法进行了一系列的placeholder替换符合操作为正确的字符串以及赋值操作,具体可以在代码中查看,然后通过JGitEnvironmentRepository的findOne()方法获取Environment类信息,存在Environment信息就直接返回,在没有Map集合信息的情况下,直接通过application, profile,label三个参数去JGitEnvironmentRepository类调用findOne()方法,即AbstractScmEnvironmentRepository类的findOne()方法,返回获取到的Environment信息。

    在下一篇文章我们将大体介绍server端的其他类,如ConfigServerEncryptionConfiguration、EncryptionAutoConfiguration。

  • spring cloud Config源码解析(二)_第2张图片
  • 大小: 50.3 KB
  • 查看图片附件

你可能感兴趣的:(spring,cloud,spring,cloud,config,spring,cloud,config,server)