前面说完了一些公共bean的配置引入,我们具体来介绍下git相关代码的实现。通过在EnvironmentRepositoryConfiguration类中,我们可以看到有两处实现git bean的地方,都是使用内部类的方式实现,分别是JGitFactoryConfig和DefaultRepositoryConfiguration,代码如下所示
@Configuration @ConditionalOnClass(TransportConfigCallback.class) static class JGitFactoryConfig { @Bean public MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory( ConfigurableEnvironment environment, ConfigServerProperties server, OptionaljgitHttpConnectionFactory, 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类,我们先看下它的继承关系图
进入到这个类,我们首先可以看到它继承自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); } Collectionoutput = 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.name、spring.cloud.config.profile、spring.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) { Listlist = 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。