【Spring Cloud】Spring Cloud Config
一.介绍
Spring Cloud Config 是SpringCloud家族中较早的配置中心,SpringCloudConfig是分布式系统中,为服务端和客户端解决配置管理的方案。它提供了集中化的配置管理,使得在不同环境(开发、测试、生成)中管理应用程序配置变得更加简单和一致。它支持配置的动态刷新,允许在不重启应用的情况下更新配置,提高了系统的灵活性和响应速度。
Spring Cloud Config 是一个分布式配置管理系统,它主要包括以下几个方面:
1)ConfigServer(配置服务器)
 Config Server 是一个配置管理服务器,负责从各种后端存储(如Git、SVN、本地文件系统等)中拉取配置信息,并提供REST API供客户端使用;
2)ConfigClient(配置客户端)
 Config Client 是应用程序中一个组件,它允许应用程序通过ConfigClient连接到ConfigServer并动
 态获取配置信息。客户端可以根据环境,服务名等动态选择对应的配置文件;
3)版本控制集成
 Spring Cloud Config 默认使用Git作为配置存储的后端,这样可以利用Git的版本控制功能来管理配置文件的版本。每个环境对应⼀个特定的版本,可以通过切换版本号来自动获取对应环境下的配置。

二.Config Server
1)首先创建一个SpringBoot项目,添加依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-config-server</artifactId>
</dependency>2)在项目启动类上添加 @EnableConfigServer 注解:
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {public static void main(String[] args) {SpringApplication.run(ConfigServerApplication.class, args);}
}
3)创建一个Git仓库:

并将配置信息上传到仓库中:
路径:config/config-server-dev.yml
data:env: config-devuser:username: config-devpassword: config-dev4)修改配置文件:
server:port: 7071
spring:application:name: config-server  # 应用名称cloud:config:server:git:uri: https://gitee.com/???/config-server.git  #配置文件Git 地址default-label: master #配置文件分支search-paths: config  #配置文件所在根目录5)Spring Cloud Config 有一套访问规则:
| /{application}/{profile}[/{label}] | 
| /{application}-{profile}.yml | 
| /{label}/{application}-{profile}.yml | 
| /{application}-{profile}.properties | 
| /{label}/{application}-{profile}.properties | 
{application}:表示微服务的名称,对应于配置中的 spring.application.name 属性;
 {profile}:表示当前环境的配置文件,如dev、test、prod等,对应于 spring.profiles.active 属性;
 {label}:表示Git仓库中的分支,标签或提交ID。这个参数是可选的,如果省略,默认会使用 mater分支。{label} 对于回滚到以前的配置版本非常有用。
使用下面的地址进行测试:127.0.0.1:7071/config-server-dev.yml

三.Config Client
Config Client 是应用程序中一个组件,它允许应用程序通过ConfigClient连接到ConfigServer并动
 态获取配置信息。客户端可以根据环境,服务名等动态选择对应的配置文件。
1)添加依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>2)配置文件
bootstrap.yml:
spring:profiles:active: devapplication:name: product-servicecloud:config:uri: http://127.0.0.1:7071  # 指定配置服务端的地址Bootstrap属性具有高优先级,也就是说在的配置会优先于 bootstrap.yml 或 bootstrap.properties 中定义 application.yml 或 application.properties 中的配置。
bootstrap.yml 主要用于配置应用启动时所需的外部依赖和环境,而 application.yml 用于业务逻辑相关的配置(如数据库连接等)。
3)给Git仓库也写入配置文件
文件名:product-service-dev.yml
data:env: product-service-dev4)读取配置
写个测试接口:
@RestController
@RequestMapping("/config")
public class ConfigController {@Value("${data.env}")private String env;@RequestMapping("/getEnv")public String getEnv(){return "data.env:" + env;}
}
访问这个接口:127.0.0.1:9090/config/getEnv

5)多平台配置支持
上面配置文件中是只有一个配置中心,所有的配置文件中都放在那里。现在有多个配置中心,那我们就不能采用之前的方法了,而是要用下面的方法:
spring:profiles:active: prodapplication:name: product-service
#多环境配置
---
spring:config:activate:on-profile: devcloud:config:uri: http://127.0.0.1:7071  # 指定配置服务端的地址---
spring:config:activate:on-profile: prodcloud:config:uri: http://127.0.0.1:7072  # 指定配置服务端的地址
四.配置中心自动刷新
1.刷新机制
Spring Cloud Config 在项目启动时加载配置内容这一机制,导致了它存在一个缺陷,修改配置文件内容后,不会自动刷新。比如上面的项目,当服务启动之后,我们修改Gitee上的配置,新的配置并不会被加载。
Spring Cloud Config问我们提供了一个刷新机制。但是这个刷新是要手动执行的,不是完全自动刷新,只是不用重新启动项目罢了。
1)引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>2)添加 @RefreshScope 注解
@RefreshScope
@RestController
@RequestMapping("/config")
public class ConfigController {@Value("${data.env}")private String env;@RequestMapping("/getEnv")public String getEnv(){return "data.env:" + env;}
}
3)开启端点
#需要开启的端点, 这里主要用到的是refresh端点, 只开启这一个就可以, 为了方便, 可以开启所有端点, 除了shutdown端点
management:endpoint:shutdown:enabled: falseendpoints:web:exposure:include: "*"4)测试
先访问127.0.0.1:9090/config/getEnv查看现在的内容:

此时我们需要手动调用http://127.0.0.1:9090/actuator/refresh这个接口,调用完成后会刷新配置:

注意,这里有一个细节是,调用的refresh这个接口必须是POST请求,GET不行。
2.WebHook
使用上面的方法刷新并不是真正实现的自动刷新,还要手动调用。如果我们想要实现完全的自动刷新,可以使用WebHook。Gitee 就提供了一种WebHook的方式,当有代码变更的时候,会调用我们设置的地址,来实现我们想达到的目的。


这里注意,输入的URL必须是公网IP,如果用127.0.0.1是不行的。
解决这个问题我们可以使用内网穿透技术,常用的软件有 ngrok 和 cpolar ,这里拿cpolar来实现。cpolar怎么用很简单,直接去官网看看就行了。
将通过内网穿透得到的URL输入上即可。添加WebHook后,当有代码变更的时候,会调用我们设置的地址,以实现自动刷新。
经过测试发现,压根就没有用。原因是webhook发送post的时候会携带其他的信息。
因此我们要使用过滤器(Filter)去过滤这些信息。
@Component
public class WebHooksFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void destroy() {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;String url = new String(httpServletRequest.getRequestURI());//检查url是否以/refresh结尾, 如果不是, 直接把请求传递给下一个过滤器或者目标资源if(!url.endsWith("/refresh")){filterChain.doFilter(servletRequest,servletResponse);return;}RequestWrapper requestWrapper = new RequestWrapper(httpServletRequest);filterChain.doFilter(requestWrapper, servletResponse);}private class RequestWrapper extends HttpServletRequestWrapper {public RequestWrapper(HttpServletRequest request) {super(request);}//重写了HttpServletRequestWrapper的getInputStream方法, 返回了一个空的字节数组的ByteArrayInputStream@Overridepublic ServletInputStream getInputStream() throws IOException {byte[] bytes = new byte[0];ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);ServletInputStream servletInputStream = new ServletInputStream() {@Overridepublic int read() throws IOException {return byteArrayInputStream.read();}@Overridepublic boolean isFinished() {return byteArrayInputStream.read() == -1 ? true : false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener listener) {}};return servletInputStream;}}
}3.Spring Cloud Bus 自动刷新
如果说此时我们启动了多个服务,这多个服务都要进行配置刷新,那就要每一个服务配一个WebHook。
Spring Cloud Bus是SpringCloud体系中的⼀个组件,主要用于在集群环境中传播分布式系统的配置变更,以及提供事件驱动的通信机制。SpringCloudBus核心原理其实就是利用消息队列做广播,目前SpringCloudBus支持两种消息代理:RabbitMQ和Kafka。
在配置文件中加入MQ相关配置,再引入依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
此时访问:localhost:9090/actuator/busrefresh(POST请求)即可同时刷新多个服务。
五.加密解密
在微服务开发中,配置文件可能包含一些敏感信息,比如数据库密码、API密码等,直接明文存储这些信息在配置文件中是非常危险的,尤其是当配置文件存储在版本控制系统(如Git)中时。这时候我们就需要对这些敏感信息进行加密。
针对这个问题,SpringCloudConfig提供了对属性进行加密解密的功能,以保护配置文件中的敏感数据不被泄露。
1.对称加密
1)添加密钥
在Config Server中添加 bootstrap.yml 设置密钥:
#对称加密
encrypt:key: 114514
2)添加bootstrap依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>3)调用接口
调用 EncryptionController 的 encrypt 和 decrypt 接口进行加密和解密。


4)具体应用
前面我们在Gitee上面添加了一些 .yml 文件,现在在文件中添加password部分:
data:env: product-service-prodpassword: '{cipher}edf62f27bc6dbc7655f1acb810003eef2004d0e4bc0a3bc193b451cdd7b5648d'在加密结果前添加{cipher},会将其解密,通过HTTP发给客户端。
这个时候我们接收到的配置就是解密过的了。
2.非对称加密
1)生成密钥
非对称加密要生成密钥对,密钥的生成我们可以使用JDK中自带的 keytool。
keytool 是Java自带的数字证书管理工具,keytool将密钥(key)和证书(certificates)存在一个称为keystore的文件中,位置在 %JAVA_HOME%\bin\keytool.exe :

直接使用命令:
 keytool -genkeypair -keystore D:/config-server.keystore  -alias config-server -keyalg RSA -keypass config -storepass config
| -genkeypair | 表示生成密钥对 | 
| -keystore | 指定密钥库的位置和名称 | 
| -alias | 表示 keystore 关联的别名 | 
| -keyalg | 表示指定密钥生成的算法 | 
| -keypass -storepass | 密钥库口令和密钥口令 | 
执行完命令后会生成一个 .keystore 文件,将这个文件放到Config Server 的resources包下。
2)添加配置
在 bootstrap.yml 添加配置:
#非对称加密配置
encrypt:key-store:location: config-server.keystore   #keystore文件存储路径alias: config-server   #密钥别名password: config      #storepass密钥仓库secret: config       #keypass 用来保护所生成密钥对中的密钥剩下的操作与对称加密一样了。
六.总结
最后做个总结。
Spring Cloud Config的作用就是配置管理,说到配置管理,就不得不想到Nacos中的配置中心。两者的区别:
1)配置存储方面:Spring Cloud Config是依赖外部版本控制系统(比如Git),而Nacos使用外部的数据库(比如MySQL)。
2)获取配置和刷新配置:对于Spring Cloud Config,客户端在ConfigServer启动时拉取配置,但是当配置更改的时候客户端接收的配置不会自动刷新,要想自动刷新,要使用Spring Cloud Bus加上消息队列(RabbitMQ)才行。
对于Nacos:客户端支持两种获取方式:拉取模式(主动轮询,可配置间隔);推送模式(服务端配置变更后主动推送,实时性更高),Nacos原生支持动态刷新。
3)多环境管理:对于Spring Cloud Config支持划分目录;对于Nacos,通过内置的命名空间和配置组进行隔离。
