Spring Cloud Config

微服务框架 Spring Cloud 核心组件 Config

Posted by leone on 2019-02-21

Spring Cloud Config

开发环境

开发工具:Intellij IDEA 2018.2.6

springboot: 2.0.6.RELEASE

jdk:1.8.0_192

maven: 3.6.0

spring-cloud-config: 2.0.2.RELEASE

Spring Cloud Config 简介

Spring Cloud Config为分布式系统中的外部化配置提供服务器和客户端支持。使用Config Server,您可以在所有环境中管理应用程序的外部属性。客户端和服务器上的概念映射与Spring Environment和PropertySource抽象,因此它们非常适合Spring应用程序,但可以与任何语言运行的任何应用程序一起使用。当应用程序通过部署管道从开发到测试并进入生产时,您可以管理这些环境之间的配置,并确保应用程序具有迁移时需要运行的所有内容。服务器存储后端的默认实现使用git,因此它可以轻松支持配置环境的标签版本,以及可用于管理内容的各种工具。添加替代实现并使用Spring配置插入它们很容易。

项目实战

config server

  • pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<packaging>jar</packaging>

<groupId>com.andy</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<version>1.0.6.RELEASE</version>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>Cairo-SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

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

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.3.RELEASE</version>
<configuration>
<!--<mainClass>${start-class}</mainClass>-->
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
  • 启动类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;


/**
* @author Leone
* @since 2017-12-07
**/
@EnableConfigServer
@ServletComponentScan
@EnableDiscoveryClient
@SpringBootApplication
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
  • application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
server:
port: 8080
spring:
application:
name: mc-config-server
cloud:
config:
server:
git:
uri: https://gitee.com/janlle/config_repo.git
# git配置文件保存的位置(启动时会拉取远程的配置)
basedir: D:\tmp\config
# 访问git仓库的用户名(非私有库可以不用配置)
# username: janlle
# 访问git仓库的密码(非私有库可以不用配置)
# password: admin
# uri: ${user.dir}\spring-cloud-config-server\src\main\resources\configs
search-paths: /
# 在启动的时候就会下载
clone-on-start: true
# git 分支
label: master
rabbitmq:
host: localhost
port: 5672
username: cloud
password: cloud
virtual-host: /mc

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
appname: service-config-server
instance-id: ${spring.application.name}:${server.port}

# spring boot 2.x actuator 的配置
management:
endpoints:
web:
exposure:
include: "*"
base-path: /actuator
health:
showDetails: always

在很多场景下,需要运行期间动态调整配置。如果配置发生了修改,微服务的配置客户端需要动态刷新配置,
所以spring cloud config 提供了/actuator/bus-refresh端口用来刷新远程的git仓库的配置,我们
可以在gitee或githu的WebHook的配置中动态的去调用config server的这个端口用来刷新config server
的配置

  • CustomerRequestWrapper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;

/**
* <p> 为了过滤掉gitee或GitHub WebHook请求的body导致的4xx报错
*
* @author leone
* @since 2019-03-30
**/
public class CustomerRequestWrapper extends HttpServletRequestWrapper {

public CustomerRequestWrapper(HttpServletRequest request) {
super(request);
}

@Override
public ServletInputStream getInputStream() throws IOException {
byte[] bytes = new byte[0];
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);

return new ServletInputStream() {
@Override
public boolean isFinished() {
return byteArrayInputStream.read() == -1;
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setReadListener(ReadListener readListener) {

}

@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
}
  • WebHookFilter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import org.springframework.core.annotation.Order;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
* <p>
*
* @author leone
* @since 2019-03-30
**/
@Order(1)
@WebFilter(filterName = "webHookFilter", urlPatterns = "/*")
public class WebHookFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
String url = httpServletRequest.getRequestURI();
// 只过滤/actuator/bus-refresh请求
if (!url.endsWith("/bus-refresh")) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
// 使用HttpServletRequest包装原始请求达到修改post请求中body内容的目的
CustomerRequestWrapper requestWrapper = new CustomerRequestWrapper(httpServletRequest);
filterChain.doFilter(requestWrapper, servletResponse);
}

@Override
public void destroy() {
}

}

config client

  • pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<packaging>jar</packaging>

<artifactId>spring-cloud-config-client</artifactId>
<groupId>com.andy</groupId>
<version>1.0.6.RELEASE</version>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>Cairo-SR6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Finchley.RC2 此版本无法访问远程git仓库-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

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

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.3.RELEASE</version>
<configuration>
<!--<mainClass>${start-class}</mainClass>-->
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
  • 启动类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
* @author Leone
* @since 2018-01-23
**/
@EnableDiscoveryClient
@SpringBootApplication
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class, args);
}
}
  • application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 9100

# 自定义默认属性
# name: defaultName
# profile: defaultProfile
# level: defaultLevel

spring:
rabbitmq:
host: localhost
port: 5672
username: cloud
password: cloud
virtual-host: /mc
publisher-confirms: true
  • bootstrap.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
spring:
application:
# 对应config server中配置文件的 {application}
name: mc-config-client
cloud:
config:
# 对应config server中配置文件的 {label}
label: master
# 访问config server的地址
uri: http://localhost:8080
# 对应config server中配置文件的 {profile}
profile: test
# username: user
# password: password
discovery:
# 表示使用服务发现组件中提供的Config Server,默认是false
# 开启通过服务发现组件访问Config Server的功能
enabled: true
#指定Config Server在服务发现组件中的serviceId
service-id: mc-config-server
enabled: true

eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
appname: service-config-client
instance-id: ${spring.application.name}:${server.port}
  • ConfigController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
* @author Leone
* @since 2018-01-23
**/
@RefreshScope
@RestController
public class ConfigController {

@Value("${profile}")
private String profile;

@Value("${name}")
private String name;

@Value("${filename}")
private String filename;

@Value("${level}")
private String level;

@GetMapping("/env")
public Map<String, String> env() {
Map<String, String> map = new HashMap<>();
map.put("profile", profile);
map.put("level", level);
map.put("filename", filename);
map.put("name", name);
return map;
}
}

github