10分钟快速入手Spring Cloud Config

Spring Cloud Config

分布式配置管理系统

作用:配置管理

主要包括:

  1. Config Server :配置管理服务器,负责从后端存储中拉取配置信息(如 git,svn,本地文件系统等),并提供 REST API 供客户端使用。
  2. Config Client:配置客户端,使程序通过 它 连接 Config Server 并动态获取配置信息。
  3. 版本控制集成:SCC 默认使用 Git 作为配置存储的后端,使用 Git 的版本控制功能来管理配置文件。每个环境对应一个特定的版本,通过切换版本号自动获取对应环境的配置。

Config Server

这是一个程序(要自己搭),步骤:

  1. 创建项目
  2. 添加依赖
  3. 项目启用Config Server (启动类上加注解)
  4. 进行配置-git 仓库地址,分支选用,配置所在文件夹
  5. 初始化 Git 仓库。

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,默认会使用 master分支, {label} 对于回滚到以前的配置版本非常有用。

Config Client

1. 引入依赖

xml 复制代码
<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>

一个是 Config 依赖,一个是 bootstrap 依赖。

Spring Cloud 会创建⼀个 Bootstrap Context , 作为Spring 应用"Application Context" 的父上下文。 在Spring应用启动的初始化阶段, Bootstrap Context 负责从外部源(如Consul)加载配置属性并解析配置。

Bootstrap 属性具有高优先级, 也就是说在 bootstrap.ymlbootstrap.properties 中定义

的配置会优先于 application.ymlapplication.properties 中的配置。

bootstrap.yml 主要用于配置应用启动时所需的外部依赖和环境, 而 application.yml 用于业
务逻辑相关的配置(如数据库连接等)

2. 配置 bootstrap 文件

yml 复制代码
spring:
	profiles: 
		active: dev 
	application:
		name: product-service 
	cloud:
		config:
			uri: http://127.0.0.1:7071 # 指定配置服务端的地址

配置 config server 的地址。

3. 测试------从配置文件中读取配置

java 复制代码
@RequestMapping("/config") 
@RestController
public class ConfigController { 
	@Value("${data.env}") 
	private String env; 
	
	@RequestMapping("/getEnv") 
	public String getEnv(){ return "env:"+ env; } }

此处从 server 中读配置,遵循前面的访问规则。

4. 多平台配置

yml 复制代码
spring: 
	profiles: 
		active: prod

#配置两个版本的配置,并通过 spring.profiles.active 设置当前使⽤的版本

--- 
spring: 
	config: 
		activate: 
			on-profile: prod 
	application: 
		name: product-service 
	cloud: 
		config: 
			uri: http://localhost:7071 
--- 
spring: 
	config: 
		activate: 
			on-profile: dev 
	application: 
			name: product-service 
	cloud: 
		config: 
			uri: http://localhost:7071

因为开发环境和生产环境,server 的地址是可能不同的。

刷新

只这样是不够的,会发现修改 git 中的配置后,读的配置不会改变。需要重启,服务才可以。

为了解决反复重启这个问题,需要借助 Actuator提供的功能。

1. 依赖引入:

xml 复制代码
<dependency>
	<groupId>org.springframework.boot</groupId> 
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2. 端点开启:

spring-boot-starter-actuator 是 Spring Boot 提供的⼀个强大的监控和管理工具,它允

许开发者查看和管理 Spring Boot 应用的各种运行时指标和状态。核心功能有健康检查, 收集和展示应用程序的运行指标, 以及端口配置

想要使用,就要开发对应的端点。

yml 复制代码
#需要开启的端点, 这⾥主要⽤到的是refresh端点, 只开启这⼀个就可以, 为了⽅便, 可以开启所有端点, 除了shutdown端点
management: 
	endpoint: 
		shutdown: 
			enabled: false 
	endpoints: 
		web: 
			exposure: 
				include: "*"

/actuator/refresh 端点是 Spring Cloud Actuator 提供的一个功能, 它允许在运行时动态刷新 Spring Cloud Config 客户端的配置

3. 注解添加:

添加 @RefreshScope 注解。

java 复制代码
@RefreshScope
@RequestMapping("/config") 
@RestController
public class ConfigController { 
	@Value("${data.env}") 
	private String env; 
	
	@RequestMapping("/getEnv") 
	public String getEnv(){ return "env:"+ env; } }

之后,每当修改后,手动动调一下接口 http://127.0.0.1:9090/actuator/refresh(POST请求)动态刷新 Spring Cloud Config 客户端的配置 (客户端服务接口)

Webhook 使用

当然,之前还是太麻烦了。每次都要手动,太不人性了。所以,引入 webhook 的使用。

webhook 是gitee 提供的,使用后。每次 push 后,gitee 会向远程 HTTP URL 发送 一个 POST 请求。这就不用手动了。

操作:进入仓库-管理-webhook-添加 webhook

直接把 http://127.0.0.1:9090/actuator/refresh 填进去是不行的,因为这是 内网的url,gitee 在外网访问不到,可以填域名 或 外网 url。

这两种比较麻烦,此处介绍一个简单的------ 内网穿透

cpolar 使用它完成。页面有提示,根据提示操作即可。

直接使用生成的 url,并测试后,会失败。

原因:webhook发送post的时候会携带其他的信息, 可以通过过滤器把对应的多余信息去掉。
Filter 、Interceptor区别

直接复制使用:

java 复制代码
package com.bite.product.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@Component
public class WebHooksFilter implements Filter {

    @Override

    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override

    public void destroy() {

    }

    @Override

    public 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

        @Override

        public ServletInputStream getInputStream() throws IOException {

            byte[] bytes = new byte[0];

            ByteArrayInputStream byteArrayInputStream = new

                    ByteArrayInputStream(bytes);

            ServletInputStream servletInputStream = new ServletInputStream() {

                @Override

                public int read() throws IOException {

                    return byteArrayInputStream.read();

                }

                @

                        Override

                public boolean isFinished() {

                    return byteArrayInputStream.read() == -1 ? true : false;

                }

                @

                        Override

                public boolean isReady() {

                    return false;

                }

                @

                        Override

                public void setReadListener(ReadListener listener) {

                }

            };

            return servletInputStream;

        }

    }

}

Spring Cloud Bus

使用 webhook 是方便了不少,但是一次提交只会刷新一个实例,而实际生产中一般会有多个实例。此时,想要更新每个实例,就要访问每个实例的 \actuator\refresh 端点,这就很麻烦。

为此引入 Spring Cloud Bus 的使用,只要用其中一个,其余也会跟着更新。

Spring Cloud Bus 核心是基于消息队列进行广播。主要用于集群环境中传播分布式系统的配置变更,以及提供事件驱动的通信机制。

目前 Spring Cloud Bus 支持的消息队列 有 RabbitMQ 和 Kafka。

依赖引入

xml 复制代码
<dependency> 
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>

这里使用 rabbitmq,如果是 kafka 则将上面的 amqp 换成 kafak。

配置添加

yml 复制代码
rabbitmq:  
  host: 49.235.103.138  
  port: 5672  
  username: admin  
  password: admin  
  virtual-host: /

使用 kafka ,则换成对应配置。

测试

启动多个实例,修改配置中心中的配置,接着访问任意实例的 /actuator/busrefresh (post 方法),可以观察到所有实例,都更新了。

加密

配置文件中有时会放一些密码之类的敏感信息,明文存储风险很大,而且还是放在git仓库里。

因此,就要对这些信息加密。

对称加密

java 中默认提供的有 一套用于加密,密钥生成的包 JCE,支持对称,非对称等加密。

但是这个默认的长度有限制。如果想要不受限制就到 https://www.oracle.com/java/technologies/javase-jce8-downloads.html 这里下载不受限的。下好后,将 local_policy.jarUS_export_policy.jar 两个文件复制到 $JAVA_HOME/jre/lib/security 目录下, 如果之前目录存在同名jar包, 则覆盖。

依赖引入

在 config server 服务中添加

xml 复制代码
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency>

配置

config server 服务中配置密钥

yml 复制代码
encrypt:  
  key: 123456

测试

访问 config server 下 \encrypt\status

如果显示 OK ,就说明成功了。

出现"The encryption algorithm is not strong enough",说明没有配密钥

出现 "java.lang.UnsupportedOperationException: No encryption for FailsafeTextEncryptor."

说明没有添加 bootstrap 依赖。

加密解密

启动 server 后,在命令行中 curl http://127.0.0.1:7071/encrypt -d root 执行此命令,即对 root 进行加密。

结果如下:9c95492304ac3a92e9d8ac2ff530b9bf1a968db65dbd14119c7f755204bba683

执行 `curl http://127.0.0.1:7071/decrypt -d 9c95492304ac3a92e9d8ac2ff530b9bf1a968db65dbd14119c7f755204bba683

就是对 root 加密的结果进行解密,结果为 root。

此处,也可使用postman, 加密对象要放在 body中,选用 raw 下的 text,且使用 POST 方法。

将加密结果放在 配置中心的配置文件中:

yml 复制代码
data:
    env: product-service-prod3
    password: '{cipher}9c95492304ac3a92e9d8ac2ff530b9bf1a968db65dbd14119c7f755204bba683'

单引号 ' ' 不可省略,且要加上 {cipher}, {cipher} 就是告诉程序这是个加密后的,获取时要用密钥解密,之后在发给 client 端。

非对称加密:

生成密钥

使用 JDK 中自带的 keytool。

命令行执行:keytool -genkeypair -keystore D:/config-server.keystore -alias config-server-keyalg RSA -keypass config -storepass config

![[Pasted image 20251109160009.png]]

接着将生成的 .keystore 文件,放在 config server 服务的 resource 文件夹下。

配置

yml 复制代码
encrypt:
	key-store: 
		location: config-server.keystore #keystore⽂件存储路径
		alias: config-server 
		password: config #密钥别名#storepass密钥仓库
		secret: config #keypass ⽤来保护所⽣成密钥对中的密钥

测试

同对称加密

加密解密

同对称加密

Faults:

非对称加密后,系统解密失败,无法获取原因

相关推荐
源码技术栈2 小时前
Java基于云计算的社区门诊系统源码 医院门诊系统源码 已实现医保结算 SaaS模式
java·云计算·源码·诊所·门诊·预约挂号·云门诊
程序员西西2 小时前
SpringBoot整合JWT实现安全认证
java·计算机·程序员·编程
袅沫3 小时前
微服务如何进行远程调用其他服务
java·微服务·架构
2501_941148153 小时前
高并发搜索引擎Elasticsearch与Solr深度优化在互联网实践分享
java·开发语言·前端
q***13344 小时前
使用 java -jar 命令启动 Spring Boot 应用时,指定特定的配置文件的几种实现方式
java·spring boot·jar
信码由缰4 小时前
Spring Data JPA 最佳实践【1/2】:实体设计指南
java
曾经的三心草5 小时前
JavaEE初阶-jvm
java·jvm·java-ee
醇氧5 小时前
Idea防止工具栏自动隐藏
java·ide·intellij-idea
on the way 1235 小时前
day01-spring底层核心解析
spring