1、流程图
要做到自动刷新配置文件,必须是文件修改之后,可以通知给客户端,我这里选择RabbitMQ
作为消息队列,让服务端和客户端可以通信。
RabbitMQ
是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ
服务器是用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。
2、Spring Cloud Bus 实现配置文件动态刷新,步骤如下:
- 提交代码,利用 Git 的 WebHooks 触发 POST 请求给 bus-refresh
- Server端接收到请求并发送给 Spring Cloud Bus
- Spring Cloud Bus 接到消息并通知给客户端
- 客户端接收到通知,请求Server端获取最新配置(全部客户端均获取到最新的配置)
3、项目
3.1 eureka-server
创建注册中心项目(这里不做详细说明,重点是 Spring Cloud Bus
)
3.2 config-server
3.2.1 引入POM文件
Spring Boot 版本 2.0.1.RELEASE
spring Cloud 版本 Finchley.RC1
这里版本必须对应,否则会有问题(可自行调试)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RC1</spring-cloud.version>
</properties>
<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.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-monitor</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
3.2.2 启动类
@EnableConfigServer
@EnableEurekaClient
@SpringBootApplication
public class MichaelSpicaConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(MichaelSpicaConfigServerApplication.class, args);
}
}
注意 @EnableConfigServer
和 @EnableEurekaClient
3.2.3 过滤器
@Slf4j
@Component
public class CustometFilter 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 = new String(httpServletRequest.getRequestURI());
//只过滤/actuator/bus-refresh请求
if (!url.endsWith("/bus-refresh")) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
//使用HttpServletRequest包装原始请求达到修改post请求中body内容的目的
CustometRequestWrapper requestWrapper = new CustometRequestWrapper(httpServletRequest);
filterChain.doFilter(requestWrapper, servletResponse);
}
@Override
public void destroy() {
}
}
public class CustometRequestWrapper extends HttpServletRequestWrapper {
public CustometRequestWrapper(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 ? true:false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
}
3.2.4 配置文件(application.yml)
spring:
application:
name: michael-spica-config-server
cloud:
config:
server:
git: #Gitee/Github
uri: https://gitee.com/xxx/config-repo.git
username: ***
password: ***
basedir: D:\project\michael\michael-spica-config-server\basedir
eureka:
client:
service-url:
defaultZone: http://localhost:8762/eureka/ #服务注册与发现
management:
endpoints:
web:
exposure:
include: "*" #暴露所有接口
项目启动后:
登录RabbitMQ
:
可以看到已经创建一个消息队列
3.3 config-client
3.3.1 引入POM文件
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RC1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
3.3.2 启动类
@SpringBootApplication
@EnableEurekaClient
public class MichaelSpicaOrderApplication {
public static void main(String[] args) {
SpringApplication.run(MichaelSpicaOrderApplication.class, args);
}
}
3.3.3 配置文件(bootstrap.yml)
spring:
application:
name: order #对应服务名
cloud:
config:
discovery:
enabled: true
service-id: CONFIG-SERVER #实例名
profile: qa #环境
eureka:
client:
service-url:
defaultZone: http://localhost:8762/eureka/
项目启动后:
刷新RabbitMQ
:
4、测试
修改远程Git库的配置文件:
手动刷新
4.1 读取配置文件
注意@RefreshScope注解(那里需要刷新在那里添加)
两种方式**(项目中通常使用“第二种”方式)**:
第一种:
@RestController
@RequestMapping("/env")
@RefreshScope
public class EnvController {
@Value("${env}")
private String env;
@GetMapping("/print")
public String print(){
return env;
}
}
第二种:
@Data
@Component
@ConfigurationProperties("girl")
@RefreshScope
public class GirlConfig {
private String name;
private Integer age;
}
4.2 通过访问接口进行测试
@RestController
@RequestMapping("/girl")
public class GirlController {
@Autowired
private GirlConfig girlConfig;
@GetMapping("/print")
public String print() {
return "name:" + girlConfig.getName() + " age:" + girlConfig.getAge();
}
}
4.2.1 访问接口
5、WebHooks 设置
URL需要一个外网可以访问的域名(可以用花生壳内网穿透)
每次PUSH代码,都会更新配置文件