前面说完了一些公共bean的配置引入,我们具体来介绍下git相关代码的实现。通过在EnvironmentRepositoryConfiguration类中,我们可以看到有两处实现git bean的地方,都是使用内部类的方式实现,分别是JGitFactoryConfig和DefaultRepositoryConfiguration,代码如下所示
@Configuration @ConditionalOnClass(TransportConfigCallback.class) static class JGitFactoryConfig { @Bean public MultipleJGitEnvironmentRepositoryFactory gitEnvironmentRepositoryFactory( ConfigurableEnvironment environment, ConfigServerProperties server, Optional<ConfigurableHttpConnectionFactory> jgitHttpConnectionFactory, Optional<TransportConfigCallback> 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); } Collection<String> output = new LinkedHashSet<String>(); 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) { List<String> list = new ArrayList<String>(); 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项目源码深度解析》 在当前的微服务架构领域,Spring Cloud以其强大的功能和易用性,成为开发者构建分布式系统的重要选择。本文将深入探讨基于Spring Cloud的项目源码,帮助读者理解其核心原理,提升...
### 二、Dubbo与SpringCloud的比较 #### 2.1 Dubbo简介 Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用、智能容错和恢复机制、以及服务自动注册和发现。 #### 2.2 ...
《Spring Cloud配置源码解析与实战》 Spring Cloud作为微服务架构的重要组件,为开发者提供了在分布式系统(如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话...
本教程将从基础入手,逐步深入到源码解析,使读者能更深入地了解和运用Spring Cloud。 首先,我们从入门开始。Spring Cloud提供了一个快速构建分布式系统工具集,包括服务发现(Eureka)、配置中心(Config Server...
1. **SpringCloud源码解析**: SpringCloud的源码分析有助于开发者了解其实现机制,从而更好地定制和优化自己的服务。源码中包含了Eureka服务发现、Zuul边缘服务、Hystrix断路器、 Ribbon客户端负载均衡、Feign声明...
总的来说,通过尚硅谷的SpringCloud源码解析和思维导图,开发者不仅可以深入了解SpringCloud的运作原理,还能提升对微服务架构设计的理解,为实际项目开发提供有力的支持。同时,对源码的学习也有助于培养解决问题和...
《SpringCloud源码解析——构建微服务架构的关键组件》 SpringCloud作为当今最热门的微服务框架之一,深受广大开发者喜爱。它集成了众多优秀的开源项目,为开发人员提供了便捷的微服务开发工具。本篇将围绕Spring...
《SpringCloud Config 源码解析与应用实践》 SpringCloud Config 是一个强大的微服务配置中心,它允许我们在运行时管理应用的配置,并且能够实时地推送到客户端。本篇文章将深入探讨 SpringCloud Config 的核心原理...
《基于SpringCloud的电商平台源码解析》 在现代互联网应用开发中,微服务架构已经成为主流。Spring Cloud作为一套完整的微服务解决方案,为开发者提供了构建分布式系统所需的工具集合,包括服务发现、配置中心、...
本篇文章将深入探讨“SpringCloud黑马商城后端代码”,解析其中的关键技术和实现细节。 首先,Spring Cloud是基于Spring Boot的一套微服务解决方案,它提供了服务注册与发现、配置中心、API网关、负载均衡、熔断器...
本资源为新手提供了一个完整的SpringCloud入门项目,包括源码、SQL脚本和详细的开发文档,非常适合想要快速了解和学习SpringCloud的新手。 1. **SpringCloud简介** SpringCloud是基于SpringBoot构建的服务治理框架...
《基于SpringCloud的分布式网上商城系统源码解析》 在当今互联网时代,电子商务系统的复杂性和规模日益增大,传统的单体架构难以应对高并发、高可用的业务需求。为了解决这些问题,开发人员开始转向分布式系统架构...
本篇将深入探讨Spring Cloud的核心组件和原理,结合"springcloud学习源码-yuanma.zip"中的源码,为你带来一次全面的学习体验。 首先,我们来了解Spring Cloud的基础知识。Spring Cloud是基于Spring Boot的微服务...
断路器示意图 SpringCloud Netflix实现了断路器库的名字叫Hystrix. 在微服务架构下,通常会有多个层次的服务调用. 下面是微服架构下, 浏览器端通过API访问后台微服务的一个示意图: hystrix 1 一个微服务的超时...
《2020最新版SpringCloud(H版&alibaba)框架开发教程》是一套全面而深入的SpringCloud学习资源,涵盖了从基础到高级的各种技术点。这套教程旨在帮助开发者掌握SpringCloud的核心概念和实践技巧,尤其针对H版及阿里...
《SpringCloud微服务实战》这本书籍的源代码包含了大量的实践示例和配置,旨在帮助读者深入理解并掌握Spring Cloud在实际开发中的应用。Spring Cloud是一个基于Spring Boot实现的服务发现、配置管理和API网关等...
总的来说,这份"Spring Cloud.pdf"学习资料涵盖了Spring Cloud的基础知识、核心组件的使用、源码解析以及实战演示,对于想要深入学习和掌握Spring Cloud的开发者来说是一份宝贵的资源。通过系统学习并实践其中的示例...
源码解析可以帮助我们理解Zuul如何处理请求,以及过滤器链的执行流程。 3. **断路器** - Hystrix是Netflix开源的断路器库,用于防止服务雪崩。当服务调用失败或响应时间过长时,断路器会打开,避免后续请求继续失败...
1. 配置中心:可能使用了Spring Cloud Config来集中管理所有服务的配置,这样可以方便地在不同环境中切换配置。 2. 服务注册与发现:可能使用Eureka或Consul等服务注册与发现组件,确保服务之间的通信。 3. 安全配置...
《疯狂springCloud实战架构》是针对企业级分布式应用开发的一款强大框架——Spring Cloud的深度解析与实战指南。Spring Cloud作为微服务生态中的重要组件,它为开发者提供了在分布式系统(如配置管理、服务发现、...