文章目录

  • 前言
  • 一、Spring Cloud Config Service
  • 引入POM文件
  • 启动配置服务
  • 基于(Native)本地配置
  • 配置类NativeEnvironmentProperties
  • 解析本地配置文件 NativeEnvironmentRepository
  • 配置文件 application.yml
  • 基于Git配置
  • 配置说明
  • 多Git URI配置
  • 配置类的抽象类
  • 解析Git存库配置类MultipleJGitEnvironmentRepository
  • 配置文件 application.yml
  • 基于JDBC配置
  • 基于JDBC存储配置类JdbcEnvironmentProperties
  • 解析JDBC配置类
  • 配置文件application.yml
  • 二、Spring Cloud Config Client
  • 引入POM文件
  • 配置文件bootstrap.yml



前言

通过统一配置服务,可以解决服务中配置文件分散,不利于管理。

一、Spring Cloud Config Service

引入POM文件

<dependencies>
   <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-config-server</artifactId>
   </dependency>
   <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
   </dependency>
</dependencies>

启动配置服务

@SpringBootApplication
//服务发现客户端
@EnableDiscoveryClient
//配置服务器端
@EnableConfigServer
public class ConfigApplication {
    public static void main(String[] args){
        SpringApplication.run(ConfigApplication.class,args);
    }
}

基于(Native)本地配置

配置类NativeEnvironmentProperties

@ConfigurationProperties("spring.cloud.config.server.native")
public class NativeEnvironmentProperties implements EnvironmentRepositoryProperties {
	/**
	 * 确定解密期间如何处理异常的标志(默认为false)
	 */
	private Boolean failOnError = false;
	/**
	 * 用于确定是否应添加分支位置的标志
	 */
	private Boolean addLabelLocations = true;
	/**
	 * 默认分支
	 */
	private String defaultLabel = "master";
	/**
	 * 搜索配置文件路径,文件格式类似Spring Boot的配置文件. 
	 * 本地文件配置如: [classpath:/,classpath:/config/,file:./,file:./config/].
	 */
	private String[] searchLocations = new String[0];
	/**
	 * 本地仓库版本号
	 */
	private String version;
	private int order = Ordered.LOWEST_PRECEDENCE;

解析本地配置文件 NativeEnvironmentRepository

重要代码如下

@Override
	public Environment findOne(String config, String profile, String label,
			boolean includeOrigin) {
		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()));

		try (ConfigurableApplicationContext context = builder.run(args)) {
			environment.getPropertySources().remove("profiles");
			return clean(new PassthruEnvironmentRepository(environment).findOne(config,
					profile, label, includeOrigin));
		}
		catch (Exception e) {
			String msg = String.format(
					"Could not construct context for config=%s profile=%s label=%s includeOrigin=%b",
					config, profile, label, includeOrigin);
			String completeMessage = NestedExceptionUtils.buildMessage(msg,
					NestedExceptionUtils.getMostSpecificCause(e));
			throw new FailedToConstructEnvironmentException(completeMessage, e);
		}
	}

	@Override
	public Locations getLocations(String application, String profile, String label) {
		String[] locations = this.searchLocations;
		if (this.searchLocations == null || this.searchLocations.length == 0) {
			locations = DEFAULT_LOCATIONS;
		}
		Collection<String> output = new LinkedHashSet<String>();

		if (label == null) {
			label = this.defaultLabel;
		}
		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 (application != 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 + "/";
					}
					if (isDirectory(value)) {
						output.add(value);
					}
				}
			}
		}
		if (this.addLabelLocations) {
			for (String location : locations) {
				if (StringUtils.hasText(label)) {
					String labelled = location + label.trim() + "/";
					if (isDirectory(labelled)) {
						output.add(labelled);
					}
				}
			}
		}
		return new Locations(application, profile, label, this.version,
				output.toArray(new String[0]));
	}

	private ConfigurableEnvironment getEnvironment(String profile) {
		ConfigurableEnvironment environment = new StandardEnvironment();
		Map<String, Object> map = new HashMap<>();
		map.put("spring.profiles.active", profile);
		map.put("spring.main.web-application-type", "none");
		environment.getPropertySources().addFirst(new MapPropertySource("profiles", map));
		return environment;
	}

	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;
	}

	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]);
	}

配置文件 application.yml

spring:
  application:
    name: config
  profiles:
    active: native
  cloud:
    config:
      server:
        native:
          search-locations: classpath:/config/
          # 默认分支  master
          default-label: master
          version: 1.0
          # 默认值 {spring.application.name}
          name: config

基于Git配置

配置说明

配置Key

默认值

说明

uri

Git URI地址

default-label

master

Git分支

timeout

5

连接Git时,如果是HTTPS是否跳过SSL验证

skip-ssl-validation

false

连接Git超时时间,默认5秒

privateKey

有效的SSH私钥。如果ignoreLocalSshSettings为true且Git URI为,则必须设置SSH 格式.

ignore-local-ssh-settings

false

如果为true,请使用基于属性的SSH配置,而不是基于文件的SSH配置。

host-key

有效的SSH主机密钥。如果还设置了hostKeyAlgorithm,则必须设置

host-key-algorithm

算法如下一种: ssh-dss, ssh-rsa, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384,ecdsa-sha2-nistp521. 如果设置了hostKey,则必须要设置

clone-on-start

false

启动时是否克隆库(按需克隆)会导致第一次启动较慢,但是第一次查询较快.

clone-submodules

false

是否克隆存储库的子模块快.

force-pull

false

指示存储库应强制拉的标志。如果为true,则丢弃任何本地,更改和从远程存储库获取

多Git URI配置

uri:为repos匹配不到时候,就会默认访问uri的资源 repos: 配置中的key,如果没有配置pattern则,pattern值为 'key/*' pattern:匹配规则,${applicationName}/${profile}和pattern中的值匹配

spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/spring-cloud-samples/config-repo
          repos:
            development:
              pattern:
                - '*/development'
                - '*/staging'
              uri: https://github.com/development/config-repo
            staging:
              pattern:
                - '*/qa'
                - '*/production'
              uri: https://github.com/staging/config-repo
配置类的抽象类
public class AbstractScmAccessorProperties implements EnvironmentRepositoryProperties {

	static final String[] DEFAULT_LOCATIONS = new String[] { "/" };

	/** 远程仓库URI.  */
	private String uri;

	/** 存储库本地工作副本的基本目录. */
	private File basedir;

	/** 搜索要在本地工作副本中使用的路径。默认情况下,只搜索根 */
	private String[] searchPaths = DEFAULT_LOCATIONS.clone();

	/**  远程仓库认知用户名. */
	private String username;

	/**  远程仓库认证密码. */
	private String password;

	/** 解锁SSH私钥的口令. */
	private String passphrase;

	/**  拒绝来自不在已知主机列表中的远程服务器的传入SSH主机密钥  */
	private boolean strictHostKeyChecking = true;

	/** 环境存储库的顺序. */
	private int order = Ordered.LOWEST_PRECEDENCE;

	/** 与远程存储库一起使用的默认标签 */
	private String defaultLabel;

解析Git存库配置类MultipleJGitEnvironmentRepository

重要代码

@Override
	public Locations getLocations(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 {
						Environment source = candidate.findOne(application, profile,
								label, false);
						if (source != null) {
							return candidate.getLocations(application, profile, label);
						}
					}
					catch (Exception e) {
						if (this.logger.isDebugEnabled()) {
							this.logger.debug("Cannot retrieve resource locations from "
									+ candidate.getUri() + ", cause: ("
									+ e.getClass().getSimpleName() + ") "
									+ e.getMessage(), e);
						}
						continue;
					}
				}
			}
		}
		JGitEnvironmentRepository candidate = getRepository(this, application, profile,
				label);
		if (candidate == this) {
			return super.getLocations(application, profile, label);
		}
		return candidate.getLocations(application, profile, label);
	}

	@Override
	public Environment findOne(String application, String profile, String label,
			boolean includeOrigin) {
		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, includeOrigin);
						if (source != null) {
							return source;
						}
					}
					catch (Exception e) {
						if (this.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, includeOrigin);
		}
		return candidate.findOne(application, profile, label, includeOrigin);
	}

	private List<JGitEnvironmentRepository> getRepositories(
			JGitEnvironmentRepository repository, String application, String profile,
			String label) {
		List<JGitEnvironmentRepository> list = new ArrayList<>();
		String[] profiles = profile == null ? new String[] { null }
				: StringUtils.commaDelimitedListToStringArray(profile);
		for (int i = profiles.length; i-- > 0;) {
			list.add(getRepository(repository, application, profiles[i], label));
		}
		return list;
	}

	JGitEnvironmentRepository getRepository(JGitEnvironmentRepository repository,
			String application, String profile, String label) {
		if (!repository.getUri().contains("{")) {
			return repository;
		}
		String key = repository.getUri();

		// cover the case where label is in the uri, but no label was sent with the
		// request
		if (key.contains("{label}") && label == null) {
			label = repository.getDefaultLabel();
		}
		if (application != null) {
			key = key.replace("{application}", application);
		}
		if (profile != null) {
			key = key.replace("{profile}", profile);
		}
		if (label != null) {
			key = key.replace("{label}", label);
		}
		if (!this.placeholders.containsKey(key)) {
			this.placeholders.put(key, getRepository(repository, key));
		}
		return this.placeholders.get(key);
	}

	private JGitEnvironmentRepository getRepository(JGitEnvironmentRepository source,
			String uri) {
		JGitEnvironmentRepository repository = new JGitEnvironmentRepository(null,
				new JGitEnvironmentProperties());
		File basedir = repository.getBasedir();
		BeanUtils.copyProperties(source, repository);
		repository.setUri(uri);
		repository.setBasedir(new File(source.getBasedir(), basedir.getName()));
		return repository;
	}

配置文件 application.yml

cloud:
    config:
      server:
        git:
          uri: git@gitee.com:swg/config.git
          private-key: xxx
          host-key-algorithm: ecdsa-sha2-nistp256
          known-hosts-file: ~/.ssh/known_hosts
          search-paths: cloud
      label: master

基于JDBC配置

基于JDBC存储配置类JdbcEnvironmentProperties

@ConfigurationProperties("spring.cloud.config.server.jdbc")
public class JdbcEnvironmentProperties implements EnvironmentRepositoryProperties {
	//默认查询配置语句
	private static final String DEFAULT_SQL = "SELECT KEY, VALUE from PROPERTIES"
			+ " where APPLICATION=? and PROFILE=? and LABEL=?";

	/**
	 * 是否启动JDBC配置
	 */
	private boolean enabled = true;

	private int order = Ordered.LOWEST_PRECEDENCE - 10;

	/** 查询数据库中key和value的SQL */
	private String sql = DEFAULT_SQL;

	/**
	 * 查询失败报异常
	 */
	private boolean failOnError = true;

解析JDBC配置类

重要代码:在这里会将 key 和value的Result 转为 map
会有一个默认的共有配置: application: application, profile: default

@Override
	public Environment findOne(String application, String profile, String label) {
		String config = application;
		if (StringUtils.isEmpty(label)) {
			label = "master";
		}
		if (StringUtils.isEmpty(profile)) {
			profile = "default";
		}
		if (!profile.startsWith("default")) {
			profile = "default," + profile;
		}
		String[] profiles = StringUtils.commaDelimitedListToStringArray(profile);
		Environment environment = new Environment(application, profiles, label, null,
				null);
		if (!config.startsWith("application")) {
			config = "application," + config;
		}
		List<String> applications = new ArrayList<String>(new LinkedHashSet<>(
				Arrays.asList(StringUtils.commaDelimitedListToStringArray(config))));
		List<String> envs = new ArrayList<String>(
				new LinkedHashSet<>(Arrays.asList(profiles)));
		Collections.reverse(applications);
		Collections.reverse(envs);
		for (String app : applications) {
			for (String env : envs) {
				try {
					Map<String, String> next = (Map<String, String>) this.jdbc.query(
							this.sql, new Object[] { app, env, label }, this.extractor);
					if (!next.isEmpty()) {
						environment.add(new PropertySource(app + "-" + env, next));
					}
				}
				catch (DataAccessException e) {
					if (!failOnError) {
						if (logger.isDebugEnabled()) {
							logger.debug(
									"Failed to retrieve configuration from JDBC Repository",
									e);
						}
					}
					else {
						throw e;
					}
				}
			}
		}
		return environment;
	}

配置文件application.yml

spring:
  application:
    name: config
  profiles:
    active: native
  cloud:
    config:
      server:
        jdbc:
        # 其中key和value 是可以随意字段只要含义相同即可
          sql: SELECT KEY, VALUE from PROPERTIES where APPLICATION=? and PROFILE=? and LABEL=?

二、Spring Cloud Config Client

引入POM文件

<dependencies>
   <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-config-client</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

配置文件bootstrap.yml

spring:
  application:
    name: config
  cloud:
    config:
      # 
      label: master
      uri: http://localhost:9100
      # 默认名称 ${spring.application.name}
      name: custmoer
      profile: dev