SpringCloud 2020笔记二
五、GateWay
- Spring生态系统之上建立的 API 网关服务
- 基于Spring FrameWork 5、Project Reactor、Spring Boot 2.0
- Spring FrameWork 5引入了新的响应式框架WebFlux(典型的异步非阻塞框架)
- 动态路由:能够匹配任何请求属性
- 可以对路由指定Predicate(断言)和Filter(过滤器)
- 集成Hystrix的断路器功能
- 集成Spring Cloud服务发现功能
- 易于编写的Predicate(断言)和Filter(过滤器)
- 请求限流功能
- 支持路径重写
1、三大核心概念
(1)Route(路由)
- 路由是构建网关的基本模块,由ID、目标URL,一系列的断言和过滤器组成,如断言为true则匹配该路由
(2)Predicate(断言)
- 参考Java的 java.util.function.Predicate
- 开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
断言规则
- id: payment_routh4 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:80 #匹配后提供服务的路由地址
uri: lb://cloud-order-service #匹配后提供服务的路由地址
predicates:
- Path=/orderController/getPaymentById/** # 断言,路径相匹配的进行路由
- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] # 在指定时间之后才可以访问路由,否则会返回404
- Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] # 在指定时间之前才可以访问路由,否则会返回404
- Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] , 2020-03-08T10:59:34.102+08:00[Asia/Shanghai] # 在指定时间段内才可以访问路由,否则会返回404
# curl http://localhost:9527/payment/lb --cookie "username=zzyy"
- Cookie=username,zzyy #Cookie=cookieName,正则表达式;请求包含指定cookie才可以访问,否则会返回404
# 请求头要有X-Request-Id属性并且值为整数的正则表达式 curl http://localhost:9527/payment/lb --cookie "username=zzyy" -H "X-Request-Id:11"
- Header=X-Request-Id, \d+ # 请求包含指定header才可以访问,否则会返回404
- Host=**.atguigu.com # curl http://localhost:9527/payment/lb -H "Host:afae.atguigu.com",指定的主机才可以访问
- Method=GET # 指定请求方式(POST、Get...),只有指定的请求方式可以访问
- Query=id, \d+ # 必须要带有参数为id,且参数值为整数才能路由(?id=90)
属性 | 属性说明 | 备注 |
---|---|---|
Path | 只有访问指定路径,才进行路由 | |
After | 可以指定,只有在指定时间后,才可以路由到指定微服务 | 获取时区:ZoneDateTime |
Before | 以指定,只有在指定时间之前,才可以路由到指定微服务 | |
Between | 需要指定两个时间,在他们之间的时间才可以访问 | |
Cookie | 只有包含某些指定cookie(key,value),的请求才可以路由 | 需要两个参数,一个是Cookie.name,一个是正则表达式,会通过获取对应的Cookie name值和正则表达式进行匹配,匹配通过则执行路由(- Cookie=username,test) |
Header | 只有包含指定请求头的请求,才可以路由 | 两个参数,一个是属性名称,一个是正则表达式,指定属性值和正则表达式匹配才会执行路由(Header=X-Request-Id, \d+) |
Host | 只有指定主机的才可以访问,比如我们当前的网站的域名是www.aa.com,那么这里就可以设置,只有用户是www.aa.com的请求才进行路由 | 可指定多个主机,主机名之间用逗号分隔,也可使用通配符匹配(**.baidu.com) |
Method | 指定请求方式(GET、POST…) | |
Query | 指定带有某个请求参数 | 一个是参数名,一个是正则表达式用于匹配参数值(Query=userId, \d+) |
(3)Filter(过滤)
- 指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改.
- 可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
- Spring Cloud GateWay 内置多种路由过滤器,由GatewayFilter 工厂类产生;
2、工作原理
- 客户端向Spring Cloud GateWay 发出请求。然后在GateWay Handler Mapping 中找到与请求匹配的路由,将其发送到GateWay Web Handler。
- Hadler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
- 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或者之后(“post”)执行业务逻辑
- Filter 在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤其中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用;
3、GateWay应用
(1)pom文件依赖
<dependencies>
<!-- SpringBoot整合Web组件 -->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 服务注册中心的客户端端 eureka-client -->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<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-gateway</artifactId>
</dependency>
</dependencies>
(2)yml文件
server:
port: 9527
eureka:
instance:
hostname: cloud-gateway-service
client:
# 表示将自己注册到eureka注册中心
register-with-eureka: true
# 是否从Eureka Server中抓取已有的注册信息,默认为true,单节点无所谓,集群必须设置为true才能配合Ribbon负载均衡
fetch-registry: true
service-url:
# defaultZone: http://localhost:7001/eureka/ 单机
defaultZone: http://eureka7001.com:7001/eureka/
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/paymentController/getPaymentById/** # 断言,路径相匹配的进行路由
- id: payment_routh2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/paymentController/getPaymentLb/** # 断言,路径相匹配的进行路由
- id: payment_routh3 #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-order-service #匹配后提供服务的路由地址
predicates:
- Path=/orderController/getPaymentLb/** # 断言,路径相匹配的进行路由
- id: payment_routh4 #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-order-service #匹配后提供服务的路由地址
predicates:
- Path=/orderController/getPaymentById/** # 断言,路径相匹配的进行路由
(3)启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @Description
* @Author xzkui
* @Date 2021/9/8 17:05
**/
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GateWayMain9527.class, args);
}
}
(4)硬编码配置
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description
* @Author xzkui
* @Date 2021/9/8 17:52
**/
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator getRouteLocator(RouteLocatorBuilder builder){
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("path_route_atguigu", r -> r.path("/guonei").uri("http://news.baidu.com/"))
.build();
return routes.build();
}
}
(5)通过微服务名转发
- 在yml配置文件中添加配置
discovery 与routes 同一层级
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
(6)自定义断言
① 新建java类,继承AbstractRoutePredicateFactory
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotNull;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
/**
* @Description 自定义的断言必须以 RoutePredicateFactory 结尾,这是断言的匹配规则
* @Author xzkui
* @Date 2021/9/10 11:42
**/
@Component
public class MyAfterRoutePredicateFactory extends AbstractRoutePredicateFactory<MyAfterRoutePredicateFactory.Config> {
/**
* 定义断言文件中需要匹配的字段
*/
public static final String DATETIME_KEY = "datetime";
public MyAfterRoutePredicateFactory() {
super(Config.class);
}
/**
* 将自定义的匹配字段以List返回,在list中的位置也表示配置填写的顺序
* @return
*/
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList(DATETIME_KEY);
}
/**
* 参数是自定义的配置类,在使用的时候配置参数,在 apply 方法中直接获取使用。
* @param config
* @return
*/
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
ZonedDateTime zonedDateTime = ZonedDateTime.now();
return zonedDateTime.isAfter(config.getDatetime());
}
@Override
public String toString() {
return String.format("MyAfter: %s", config.getDatetime());
}
};
}
/**
* 定义配置类
*/
public static class Config {
@NotNull
private ZonedDateTime datetime;
public Config() {
}
public ZonedDateTime getDatetime() {
return this.datetime;
}
public void setDatetime(ZonedDateTime datetime) {
this.datetime = datetime;
}
}
}
② 修改配置文件
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/paymentController/getPaymentById/** # 断言,路径相匹配的进行路由
- MyAfter=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
5、AbstractRoutePredicateFactory
- 是一个抽象类,继承 AbstractConfigurable 类,并实现了RoutePredicateFactory 接口
(1)AbstractRoutePredicateFactory源码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.handler.predicate;
import org.springframework.cloud.gateway.support.AbstractConfigurable;
public abstract class AbstractRoutePredicateFactory<C> extends AbstractConfigurable<C> implements RoutePredicateFactory<C> {
public AbstractRoutePredicateFactory(Class<C> configClass) {
super(configClass);
}
}
(2)AbstractConfigurable类
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.support;
import org.springframework.beans.BeanUtils;
import org.springframework.core.style.ToStringCreator;
public abstract class AbstractConfigurable<C> implements Configurable<C> {
private Class<C> configClass;
protected AbstractConfigurable(Class<C> configClass) {
this.configClass = configClass;
}
public Class<C> getConfigClass() {
return this.configClass;
}
public C newConfig() {
return BeanUtils.instantiateClass(this.configClass);
}
public String toString() {
return (new ToStringCreator(this)).append("configClass", this.configClass).toString();
}
}
(3)RoutePredicateFactory接口
- @FunctionalInterface:函数式接口
- 接口有且仅有一个抽象方法
- 允许定义静态方法
- 允许定义默认方法
- 允许java.lang.Object中的public方法
- 该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。
- 如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.handler.predicate;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.support.Configurable;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.cloud.gateway.support.ShortcutConfigurable;
import org.springframework.web.server.ServerWebExchange;
@FunctionalInterface
public interface RoutePredicateFactory<C> extends ShortcutConfigurable, Configurable<C> {
String PATTERN_KEY = "pattern";
default Predicate<ServerWebExchange> apply(Consumer<C> consumer) {
C config = this.newConfig();
consumer.accept(config);
this.beforeApply(config);
return this.apply(config);
}
default AsyncPredicate<ServerWebExchange> applyAsync(Consumer<C> consumer) {
C config = this.newConfig();
consumer.accept(config);
this.beforeApply(config);
return this.applyAsync(config);
}
default Class<C> getConfigClass() {
throw new UnsupportedOperationException("getConfigClass() not implemented");
}
default C newConfig() {
throw new UnsupportedOperationException("newConfig() not implemented");
}
default void beforeApply(C config) {
}
Predicate<ServerWebExchange> apply(C config);
default AsyncPredicate<ServerWebExchange> applyAsync(C config) {
return ServerWebExchangeUtils.toAsyncPredicate(this.apply(config));
}
default String name() {
return NameUtils.normalizeRoutePredicateName(this.getClass());
}
}
6、SpringCloudGateWay 的十二个断言实现类
- 都继承自 AbstractRoutePredicateFactory 这个抽象类
(1)AfterRoutePredicateFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.handler.predicate;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import javax.validation.constraints.NotNull;
import org.springframework.web.server.ServerWebExchange;
public class AfterRoutePredicateFactory extends AbstractRoutePredicateFactory<AfterRoutePredicateFactory.Config> {
public static final String DATETIME_KEY = "datetime";
public AfterRoutePredicateFactory() {
super(AfterRoutePredicateFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Collections.singletonList("datetime");
}
public Predicate<ServerWebExchange> apply(AfterRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange serverWebExchange) {
ZonedDateTime now = ZonedDateTime.now();
return now.isAfter(config.getDatetime());
}
public String toString() {
return String.format("After: %s", config.getDatetime());
}
};
}
public static class Config {
@NotNull
private ZonedDateTime datetime;
public Config() {
}
public ZonedDateTime getDatetime() {
return this.datetime;
}
public void setDatetime(ZonedDateTime datetime) {
this.datetime = datetime;
}
}
}
(2)BeforeRoutePredicateFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.handler.predicate;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import org.springframework.web.server.ServerWebExchange;
public class BeforeRoutePredicateFactory extends AbstractRoutePredicateFactory<BeforeRoutePredicateFactory.Config> {
public static final String DATETIME_KEY = "datetime";
public BeforeRoutePredicateFactory() {
super(BeforeRoutePredicateFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Collections.singletonList("datetime");
}
public Predicate<ServerWebExchange> apply(BeforeRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange serverWebExchange) {
ZonedDateTime now = ZonedDateTime.now();
return now.isBefore(config.getDatetime());
}
public String toString() {
return String.format("Before: %s", config.getDatetime());
}
};
}
public static class Config {
private ZonedDateTime datetime;
public Config() {
}
public ZonedDateTime getDatetime() {
return this.datetime;
}
public void setDatetime(ZonedDateTime datetime) {
this.datetime = datetime;
}
}
}
(3)BetweenRoutePredicateFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.handler.predicate;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import javax.validation.constraints.NotNull;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
public class BetweenRoutePredicateFactory extends AbstractRoutePredicateFactory<BetweenRoutePredicateFactory.Config> {
public static final String DATETIME1_KEY = "datetime1";
public static final String DATETIME2_KEY = "datetime2";
public BetweenRoutePredicateFactory() {
super(BetweenRoutePredicateFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("datetime1", "datetime2");
}
public Predicate<ServerWebExchange> apply(BetweenRoutePredicateFactory.Config config) {
Assert.isTrue(config.getDatetime1().isBefore(config.getDatetime2()), config.getDatetime1() + " must be before " + config.getDatetime2());
return new GatewayPredicate() {
public boolean test(ServerWebExchange serverWebExchange) {
ZonedDateTime now = ZonedDateTime.now();
return now.isAfter(config.getDatetime1()) && now.isBefore(config.getDatetime2());
}
public String toString() {
return String.format("Between: %s and %s", config.getDatetime1(), config.getDatetime2());
}
};
}
@Validated
public static class Config {
@NotNull
private ZonedDateTime datetime1;
@NotNull
private ZonedDateTime datetime2;
public Config() {
}
public ZonedDateTime getDatetime1() {
return this.datetime1;
}
public BetweenRoutePredicateFactory.Config setDatetime1(ZonedDateTime datetime1) {
this.datetime1 = datetime1;
return this;
}
public ZonedDateTime getDatetime2() {
return this.datetime2;
}
public BetweenRoutePredicateFactory.Config setDatetime2(ZonedDateTime datetime2) {
this.datetime2 = datetime2;
return this;
}
}
}
(4)CloudFoundryRouteServiceRoutePredicateFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.handler.predicate;
import java.util.function.Predicate;
import org.springframework.cloud.gateway.handler.predicate.HeaderRoutePredicateFactory.Config;
import org.springframework.web.server.ServerWebExchange;
public class CloudFoundryRouteServiceRoutePredicateFactory extends AbstractRoutePredicateFactory<Object> {
public static final String X_CF_FORWARDED_URL = "X-CF-Forwarded-Url";
public static final String X_CF_PROXY_SIGNATURE = "X-CF-Proxy-Signature";
public static final String X_CF_PROXY_METADATA = "X-CF-Proxy-Metadata";
private final HeaderRoutePredicateFactory factory = new HeaderRoutePredicateFactory();
public CloudFoundryRouteServiceRoutePredicateFactory() {
super(Object.class);
}
public Predicate<ServerWebExchange> apply(Object unused) {
return this.headerPredicate("X-CF-Forwarded-Url").and(this.headerPredicate("X-CF-Proxy-Signature")).and(this.headerPredicate("X-CF-Proxy-Metadata"));
}
private Predicate<ServerWebExchange> headerPredicate(String header) {
Config config = (Config)this.factory.newConfig();
config.setHeader(header);
config.setRegexp(".*");
return this.factory.apply(config);
}
}
(5)CookieRoutePredicateFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.handler.predicate;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import javax.validation.constraints.NotEmpty;
import org.springframework.http.HttpCookie;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
public class CookieRoutePredicateFactory extends AbstractRoutePredicateFactory<CookieRoutePredicateFactory.Config> {
public static final String NAME_KEY = "name";
public static final String REGEXP_KEY = "regexp";
public CookieRoutePredicateFactory() {
super(CookieRoutePredicateFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("name", "regexp");
}
public Predicate<ServerWebExchange> apply(CookieRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange exchange) {
List<HttpCookie> cookies = (List)exchange.getRequest().getCookies().get(config.name);
if (cookies == null) {
return false;
} else {
Iterator var3 = cookies.iterator();
HttpCookie cookie;
do {
if (!var3.hasNext()) {
return false;
}
cookie = (HttpCookie)var3.next();
} while(!cookie.getValue().matches(config.regexp));
return true;
}
}
public String toString() {
return String.format("Cookie: name=%s regexp=%s", config.name, config.regexp);
}
};
}
@Validated
public static class Config {
@NotEmpty
private String name;
@NotEmpty
private String regexp;
public Config() {
}
public String getName() {
return this.name;
}
public CookieRoutePredicateFactory.Config setName(String name) {
this.name = name;
return this;
}
public String getRegexp() {
return this.regexp;
}
public CookieRoutePredicateFactory.Config setRegexp(String regexp) {
this.regexp = regexp;
return this;
}
}
}
(6)HeaderRoutePredicateFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.handler.predicate;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import javax.validation.constraints.NotEmpty;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
public class HeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<HeaderRoutePredicateFactory.Config> {
public static final String HEADER_KEY = "header";
public static final String REGEXP_KEY = "regexp";
public HeaderRoutePredicateFactory() {
super(HeaderRoutePredicateFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("header", "regexp");
}
public Predicate<ServerWebExchange> apply(HeaderRoutePredicateFactory.Config config) {
final boolean hasRegex = !StringUtils.isEmpty(config.regexp);
return new GatewayPredicate() {
public boolean test(ServerWebExchange exchange) {
List<String> values = (List)exchange.getRequest().getHeaders().getOrDefault(config.header, Collections.emptyList());
if (values.isEmpty()) {
return false;
} else {
return hasRegex ? values.stream().anyMatch((value) -> {
return value.matches(config.regexp);
}) : true;
}
}
public String toString() {
return String.format("Header: %s regexp=%s", config.header, config.regexp);
}
};
}
@Validated
public static class Config {
@NotEmpty
private String header;
private String regexp;
public Config() {
}
public String getHeader() {
return this.header;
}
public HeaderRoutePredicateFactory.Config setHeader(String header) {
this.header = header;
return this;
}
public String getRegexp() {
return this.regexp;
}
public HeaderRoutePredicateFactory.Config setRegexp(String regexp) {
this.regexp = regexp;
return this;
}
}
}
(7)MethodRoutePredicateFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.handler.predicate;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType;
import org.springframework.http.HttpMethod;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
public class MethodRoutePredicateFactory extends AbstractRoutePredicateFactory<MethodRoutePredicateFactory.Config> {
/** @deprecated */
@Deprecated
public static final String METHOD_KEY = "method";
public static final String METHODS_KEY = "methods";
public MethodRoutePredicateFactory() {
super(MethodRoutePredicateFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("methods");
}
public ShortcutType shortcutType() {
return ShortcutType.GATHER_LIST;
}
public Predicate<ServerWebExchange> apply(MethodRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange exchange) {
HttpMethod requestMethod = exchange.getRequest().getMethod();
return Arrays.stream(config.getMethods()).anyMatch((httpMethod) -> {
return httpMethod == requestMethod;
});
}
public String toString() {
return String.format("Methods: %s", Arrays.toString(config.getMethods()));
}
};
}
@Validated
public static class Config {
private HttpMethod[] methods;
public Config() {
}
/** @deprecated */
@Deprecated
public HttpMethod getMethod() {
return this.methods != null && this.methods.length > 0 ? this.methods[0] : null;
}
/** @deprecated */
@Deprecated
public void setMethod(HttpMethod method) {
this.methods = new HttpMethod[]{method};
}
public HttpMethod[] getMethods() {
return this.methods;
}
public void setMethods(HttpMethod... methods) {
this.methods = methods;
}
}
}
(8)HostRoutePredicateFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.handler.predicate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
public class HostRoutePredicateFactory extends AbstractRoutePredicateFactory<HostRoutePredicateFactory.Config> {
private PathMatcher pathMatcher = new AntPathMatcher(".");
public HostRoutePredicateFactory() {
super(HostRoutePredicateFactory.Config.class);
}
public void setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
}
public List<String> shortcutFieldOrder() {
return Collections.singletonList("patterns");
}
public ShortcutType shortcutType() {
return ShortcutType.GATHER_LIST;
}
public Predicate<ServerWebExchange> apply(HostRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange exchange) {
String host = exchange.getRequest().getHeaders().getFirst("Host");
Optional<String> optionalPattern = config.getPatterns().stream().filter((pattern) -> {
return HostRoutePredicateFactory.this.pathMatcher.match(pattern, host);
}).findFirst();
if (optionalPattern.isPresent()) {
Map<String, String> variables = HostRoutePredicateFactory.this.pathMatcher.extractUriTemplateVariables((String)optionalPattern.get(), host);
ServerWebExchangeUtils.putUriTemplateVariables(exchange, variables);
return true;
} else {
return false;
}
}
public String toString() {
return String.format("Hosts: %s", config.getPatterns());
}
};
}
@Validated
public static class Config {
private List<String> patterns = new ArrayList();
public Config() {
}
/** @deprecated */
@Deprecated
public String getPattern() {
return !CollectionUtils.isEmpty(this.patterns) ? (String)this.patterns.get(0) : null;
}
/** @deprecated */
@Deprecated
public HostRoutePredicateFactory.Config setPattern(String pattern) {
this.patterns = new ArrayList();
this.patterns.add(pattern);
return this;
}
public List<String> getPatterns() {
return this.patterns;
}
public HostRoutePredicateFactory.Config setPatterns(List<String> patterns) {
this.patterns = patterns;
return this;
}
public String toString() {
return (new ToStringCreator(this)).append("patterns", this.patterns).toString();
}
}
}
(9)PathRoutePredicateFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.handler.predicate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType;
import org.springframework.core.style.ToStringCreator;
import org.springframework.http.server.PathContainer;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
import org.springframework.web.util.pattern.PathPattern.PathMatchInfo;
public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> {
private static final Log log = LogFactory.getLog(RoutePredicateFactory.class);
private static final String MATCH_OPTIONAL_TRAILING_SEPARATOR_KEY = "matchOptionalTrailingSeparator";
private PathPatternParser pathPatternParser = new PathPatternParser();
public PathRoutePredicateFactory() {
super(PathRoutePredicateFactory.Config.class);
}
private static void traceMatch(String prefix, Object desired, Object actual, boolean match) {
if (log.isTraceEnabled()) {
String message = String.format("%s \"%s\" %s against value \"%s\"", prefix, desired, match ? "matches" : "does not match", actual);
log.trace(message);
}
}
public void setPathPatternParser(PathPatternParser pathPatternParser) {
this.pathPatternParser = pathPatternParser;
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("patterns", "matchOptionalTrailingSeparator");
}
public ShortcutType shortcutType() {
return ShortcutType.GATHER_LIST_TAIL_FLAG;
}
public Predicate<ServerWebExchange> apply(PathRoutePredicateFactory.Config config) {
final ArrayList<PathPattern> pathPatterns = new ArrayList();
synchronized(this.pathPatternParser) {
this.pathPatternParser.setMatchOptionalTrailingSeparator(config.isMatchOptionalTrailingSeparator());
config.getPatterns().forEach((pattern) -> {
PathPattern pathPattern = this.pathPatternParser.parse(pattern);
pathPatterns.add(pathPattern);
});
}
return new GatewayPredicate() {
public boolean test(ServerWebExchange exchange) {
PathContainer path = PathContainer.parsePath(exchange.getRequest().getURI().getRawPath());
Optional<PathPattern> optionalPathPattern = pathPatterns.stream().filter((pattern) -> {
return pattern.matches(path);
}).findFirst();
if (optionalPathPattern.isPresent()) {
PathPattern pathPattern = (PathPattern)optionalPathPattern.get();
PathRoutePredicateFactory.traceMatch("Pattern", pathPattern.getPatternString(), path, true);
PathMatchInfo pathMatchInfo = pathPattern.matchAndExtract(path);
ServerWebExchangeUtils.putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables());
return true;
} else {
PathRoutePredicateFactory.traceMatch("Pattern", config.getPatterns(), path, false);
return false;
}
}
public String toString() {
return String.format("Paths: %s, match trailing slash: %b", config.getPatterns(), config.isMatchOptionalTrailingSeparator());
}
};
}
@Validated
public static class Config {
private List<String> patterns = new ArrayList();
private boolean matchOptionalTrailingSeparator = true;
public Config() {
}
/** @deprecated */
@Deprecated
public String getPattern() {
return !CollectionUtils.isEmpty(this.patterns) ? (String)this.patterns.get(0) : null;
}
/** @deprecated */
@Deprecated
public PathRoutePredicateFactory.Config setPattern(String pattern) {
this.patterns = new ArrayList();
this.patterns.add(pattern);
return this;
}
public List<String> getPatterns() {
return this.patterns;
}
public PathRoutePredicateFactory.Config setPatterns(List<String> patterns) {
this.patterns = patterns;
return this;
}
public boolean isMatchOptionalTrailingSeparator() {
return this.matchOptionalTrailingSeparator;
}
public PathRoutePredicateFactory.Config setMatchOptionalTrailingSeparator(boolean matchOptionalTrailingSeparator) {
this.matchOptionalTrailingSeparator = matchOptionalTrailingSeparator;
return this;
}
public String toString() {
return (new ToStringCreator(this)).append("patterns", this.patterns).append("matchOptionalTrailingSeparator", this.matchOptionalTrailingSeparator).toString();
}
}
}
(10)QueryRoutePredicateFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.handler.predicate;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import javax.validation.constraints.NotEmpty;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory<QueryRoutePredicateFactory.Config> {
public static final String PARAM_KEY = "param";
public static final String REGEXP_KEY = "regexp";
public QueryRoutePredicateFactory() {
super(QueryRoutePredicateFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("param", "regexp");
}
public Predicate<ServerWebExchange> apply(QueryRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange exchange) {
if (!StringUtils.hasText(config.regexp)) {
return exchange.getRequest().getQueryParams().containsKey(config.param);
} else {
List<String> values = (List)exchange.getRequest().getQueryParams().get(config.param);
if (values == null) {
return false;
} else {
Iterator var3 = values.iterator();
String value;
do {
if (!var3.hasNext()) {
return false;
}
value = (String)var3.next();
} while(value == null || !value.matches(config.regexp));
return true;
}
}
}
public String toString() {
return String.format("Query: param=%s regexp=%s", config.getParam(), config.getRegexp());
}
};
}
@Validated
public static class Config {
@NotEmpty
private String param;
private String regexp;
public Config() {
}
public String getParam() {
return this.param;
}
public QueryRoutePredicateFactory.Config setParam(String param) {
this.param = param;
return this;
}
public String getRegexp() {
return this.regexp;
}
public QueryRoutePredicateFactory.Config setRegexp(String regexp) {
this.regexp = regexp;
return this;
}
}
}
(11)ReadBodyPredicateFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.handler.predicate;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
public class ReadBodyPredicateFactory extends AbstractRoutePredicateFactory<ReadBodyPredicateFactory.Config> {
protected static final Log log = LogFactory.getLog(ReadBodyPredicateFactory.class);
private static final String TEST_ATTRIBUTE = "read_body_predicate_test_attribute";
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();
public ReadBodyPredicateFactory() {
super(ReadBodyPredicateFactory.Config.class);
}
public AsyncPredicate<ServerWebExchange> applyAsync(ReadBodyPredicateFactory.Config config) {
return new AsyncPredicate<ServerWebExchange>() {
public Publisher<Boolean> apply(ServerWebExchange exchange) {
Class inClass = config.getInClass();
Object cachedBody = exchange.getAttribute("cachedRequestBodyObject");
if (cachedBody != null) {
try {
boolean test = config.predicate.test(cachedBody);
exchange.getAttributes().put("read_body_predicate_test_attribute", test);
return Mono.just(test);
} catch (ClassCastException var6) {
if (ReadBodyPredicateFactory.log.isDebugEnabled()) {
ReadBodyPredicateFactory.log.debug("Predicate test failed because class in predicate does not match the cached body object", var6);
}
return Mono.just(false);
}
} else {
return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> {
return ServerRequest.create(exchange.mutate().request(serverHttpRequest).build(), ReadBodyPredicateFactory.messageReaders).bodyToMono(inClass).doOnNext((objectValue) -> {
exchange.getAttributes().put("cachedRequestBodyObject", objectValue);
}).map((objectValue) -> {
return config.getPredicate().test(objectValue);
});
});
}
}
public String toString() {
return String.format("ReadBody: %s", config.getInClass());
}
};
}
public Predicate<ServerWebExchange> apply(ReadBodyPredicateFactory.Config config) {
throw new UnsupportedOperationException("ReadBodyPredicateFactory is only async.");
}
public static class Config {
private Class inClass;
private Predicate predicate;
private Map<String, Object> hints;
public Config() {
}
public Class getInClass() {
return this.inClass;
}
public ReadBodyPredicateFactory.Config setInClass(Class inClass) {
this.inClass = inClass;
return this;
}
public Predicate getPredicate() {
return this.predicate;
}
public ReadBodyPredicateFactory.Config setPredicate(Predicate predicate) {
this.predicate = predicate;
return this;
}
public <T> ReadBodyPredicateFactory.Config setPredicate(Class<T> inClass, Predicate<T> predicate) {
this.setInClass(inClass);
this.predicate = predicate;
return this;
}
public Map<String, Object> getHints() {
return this.hints;
}
public ReadBodyPredicateFactory.Config setHints(Map<String, Object> hints) {
this.hints = hints;
return this;
}
}
}
(12)RemoteAddrRoutePredicateFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.handler.predicate;
import io.netty.handler.ipfilter.IpFilterRuleType;
import io.netty.handler.ipfilter.IpSubnetFilterRule;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType;
import org.springframework.cloud.gateway.support.ipresolver.RemoteAddressResolver;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
public class RemoteAddrRoutePredicateFactory extends AbstractRoutePredicateFactory<RemoteAddrRoutePredicateFactory.Config> {
private static final Log log = LogFactory.getLog(RemoteAddrRoutePredicateFactory.class);
public RemoteAddrRoutePredicateFactory() {
super(RemoteAddrRoutePredicateFactory.Config.class);
}
public ShortcutType shortcutType() {
return ShortcutType.GATHER_LIST;
}
public List<String> shortcutFieldOrder() {
return Collections.singletonList("sources");
}
@NotNull
private List<IpSubnetFilterRule> convert(List<String> values) {
List<IpSubnetFilterRule> sources = new ArrayList();
Iterator var3 = values.iterator();
while(var3.hasNext()) {
String arg = (String)var3.next();
this.addSource(sources, arg);
}
return sources;
}
public Predicate<ServerWebExchange> apply(RemoteAddrRoutePredicateFactory.Config config) {
final List<IpSubnetFilterRule> sources = this.convert(config.sources);
return new GatewayPredicate() {
public boolean test(ServerWebExchange exchange) {
InetSocketAddress remoteAddress = config.remoteAddressResolver.resolve(exchange);
if (remoteAddress != null && remoteAddress.getAddress() != null) {
String hostAddress = remoteAddress.getAddress().getHostAddress();
String host = exchange.getRequest().getURI().getHost();
if (RemoteAddrRoutePredicateFactory.log.isDebugEnabled() && !hostAddress.equals(host)) {
RemoteAddrRoutePredicateFactory.log.debug("Remote addresses didn't match " + hostAddress + " != " + host);
}
Iterator var5 = sources.iterator();
while(var5.hasNext()) {
IpSubnetFilterRule source = (IpSubnetFilterRule)var5.next();
if (source.matches(remoteAddress)) {
return true;
}
}
}
return false;
}
public String toString() {
return String.format("RemoteAddrs: %s", config.getSources());
}
};
}
private void addSource(List<IpSubnetFilterRule> sources, String source) {
if (!source.contains("/")) {
source = source + "/32";
}
String[] ipAddressCidrPrefix = source.split("/", 2);
String ipAddress = ipAddressCidrPrefix[0];
int cidrPrefix = Integer.parseInt(ipAddressCidrPrefix[1]);
sources.add(new IpSubnetFilterRule(ipAddress, cidrPrefix, IpFilterRuleType.ACCEPT));
}
@Validated
public static class Config {
@NotEmpty
private List<String> sources = new ArrayList();
@NotNull
private RemoteAddressResolver remoteAddressResolver = new RemoteAddressResolver() {
};
public Config() {
}
public List<String> getSources() {
return this.sources;
}
public RemoteAddrRoutePredicateFactory.Config setSources(List<String> sources) {
this.sources = sources;
return this;
}
public RemoteAddrRoutePredicateFactory.Config setSources(String... sources) {
this.sources = Arrays.asList(sources);
return this;
}
public RemoteAddrRoutePredicateFactory.Config setRemoteAddressResolver(RemoteAddressResolver remoteAddressResolver) {
this.remoteAddressResolver = remoteAddressResolver;
return this;
}
}
}
(13)WeightRoutePredicateFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.cloud.gateway.handler.predicate;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.event.WeightDefinedEvent;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.cloud.gateway.support.WeightConfig;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.web.server.ServerWebExchange;
public class WeightRoutePredicateFactory extends AbstractRoutePredicateFactory<WeightConfig> implements ApplicationEventPublisherAware {
public static final String GROUP_KEY = "weight.group";
public static final String WEIGHT_KEY = "weight.weight";
private static final Log log = LogFactory.getLog(WeightRoutePredicateFactory.class);
private ApplicationEventPublisher publisher;
public WeightRoutePredicateFactory() {
super(WeightConfig.class);
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("weight.group", "weight.weight");
}
public String shortcutFieldPrefix() {
return "weight";
}
public void beforeApply(WeightConfig config) {
if (this.publisher != null) {
this.publisher.publishEvent(new WeightDefinedEvent(this, config));
}
}
public Predicate<ServerWebExchange> apply(WeightConfig config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange exchange) {
Map<String, String> weights = (Map)exchange.getAttributeOrDefault(ServerWebExchangeUtils.WEIGHT_ATTR, Collections.emptyMap());
String routeId = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
String group = config.getGroup();
if (weights.containsKey(group)) {
String chosenRoute = (String)weights.get(group);
if (WeightRoutePredicateFactory.log.isTraceEnabled()) {
WeightRoutePredicateFactory.log.trace("in group weight: " + group + ", current route: " + routeId + ", chosen route: " + chosenRoute);
}
return routeId.equals(chosenRoute);
} else {
if (WeightRoutePredicateFactory.log.isTraceEnabled()) {
WeightRoutePredicateFactory.log.trace("no weights found for group: " + group + ", current route: " + routeId);
}
return false;
}
}
public String toString() {
return String.format("Weight: %s %s", config.getGroup(), config.getWeight());
}
};
}
}
六、SpringCloud Config
- 分布式配置中心
- 分布式系统面临的问题
- 微服务意味着要将单体应用中的业务拆分成多个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息,所以当微服务数量庞大时,配置文件管理就会变得麻烦,Spring cloud config便是用来解决这个问题;
- 微服务意味着要将单体应用中的业务拆分成多个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息,所以当微服务数量庞大时,配置文件管理就会变得麻烦,Spring cloud config便是用来解决这个问题;
1、服务端
- 分布式配置中心,是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
(1)、pom文件
<!-- SpringBoot整合Web组件 -->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<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.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Config服务端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
(2)、yml配置文件
- 添加配置文件在gitee的地址
spring:
application:
name: cloud-config-server
cloud:
config:
server:
git:
uri: https://gitee.com/xiao-zhikui/spring-cloud2020-config.git # 仓库地址
search-paths:
- spring-cloud2020-config # 配置文件存放目录
label: master # 读取分支
(3)、启动类
- 添加注解:@EnableConfigServer
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
/**
* @Description
* @Author xzkui
* @Date 2021/9/10 18:13
**/
@SpringBootApplication
@EnableConfigServer
public class CloudConfigMain3344 {
public static void main(String[] args) {
SpringApplication.run(CloudConfigMain3344.class, args);
}
}
(4)、获取配置文件信息
- http://config-3344.com:3344/config-dev.yml
读取规则
① 指定分支
② 不指定分支,会默认读取配置文件中配置的分支
2、客户端
- 通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息,配置服务器默认采用git来存储配置信息;
(1)pom文件
<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.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
(2)yml配置文件
- 使用bootstrap.yml,优先级比application.yml高
server:
port: 3355
spring:
application:
name: config-client
cloud:
# config客户端配置,以下四个配置组合起来就是,客户端将读取master分支下config-dev.yml配置文件:http://config-3344.com:3344/master/config-dev.yml
config:
label: master # 分支名称
name: config # 配置文件名称
profile: dev # 读取文件后缀
uri: http://config-3344.com:3344/ # 配置中心地址
(3)启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @Description
* @Author xzkui
* @Date 2021/9/13 16:32
**/
@SpringBootApplication
public class CloudConfigClient3355 {
public static void main(String[] args) {
SpringApplication.run(CloudConfigClient3355.class, args);
}
}
(4)获取配置文件信息
- http://localhost:3355/cloudConfigClient/getConfigInfo
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description
* @Author xzkui
* @Date 2021/9/13 16:32
**/
@RestController
@RequestMapping("/cloudConfigClient")
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping(value = "/getConfigInfo")
public String getConfigInfo(){
return configInfo;
}
}
3、问题
- 当配置文件进行了修改,服务端可以实时获取配置文件,但是客户端无法实时获取,除非重启客户端
4、实时刷新,客户端调整
(1)添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
(2)bootstrap.yml添加配置
# 暴露监控点
management:
endpoints:
web:
exposure:
include: "*"
(3)controller添加注解
- @RefreshScope
- import org.springframework.cloud.context.config.annotation.RefreshScope;
(4)具体流程
- 修改配置文件
- 服务端发送一个POST请求,告诉客户端配置已修改(使用windows命令行)
- curl -X POST “http://localhost:3355/actuator/refresh/”
- 客户端再次获取配置文件,数据为最新
七、SpringCloud Bus
- 使用Spring Cloud Config的实时刷新需要逐个服务通知,如果微服务数量较多,则较为麻烦,使用Spring Cloud Bus解决
- 实现配置的动态刷新
- 支持RabbitMQ、Kafka
- 可以单独发送,也可广播发送给所有服务
- 需要安装Rabbit MQ
1、服务端调整
(1)pom.xml
<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
(2)yml文件
#SpringCloud Bus动态刷新定点通知 公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}
#例如 只通知3355,curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"
##rabbitmq相关配置,暴露bus刷新配置的端点
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: gues
# 暴露监控点
management:
endpoints:
web:
exposure:
include: "bus-refresh"
2、客户端调整
(1)pom.xml
<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
(2)bootstrap.yml
# 暴露监控点
management:
endpoints:
web:
exposure:
include: "*"
#SpringCloud Bus动态刷新定点通知 公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}
#例如 只通知3355,curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"
##rabbitmq相关配置,暴露bus刷新配置的端点
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: gues
3、具体流程
- 修改配置文件
- 服务端发送一个POST请求,告诉客户端配置已修改(使用windows命令行)
- curl -X POST “http://localhost:3344/actuator/bus-refresh/”
- 客户端再次获取配置文件,数据为最新
- 告诉单个客户端,需要指定:curl -X POST “http://localhost:3344/actuator/bus-refresh/config-client:3366”
4、数据库配置刷新
(1) 修改数据库配置后,如何实时刷新
- 需要添加以下配置代码
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
/**
* @Description
* @Author xzkui
* @Date 2021/9/14 12:00
**/
@Configuration
public class RefreshConfig {
/**
* 数据库实时刷新配置
* @return
*/
@Bean
@Primary
@RefreshScope
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource refreshDataSource(){
return DataSourceBuilder.create().build();
}
}
- gitee 上配置文件
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
# springboot 2.x 的数据库url换成jdbc-url,但是url不可少,否则启动报异常,修改数据读取根据jdbc-url连接
jdbc-url: jdbc:mysql://localhost:3306/db2019_copy?useUnicode=true&characterEncoding=utf-8&useSSL=false
url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123
eureka:
client:
# 表示将自己注册到eureka注册中心
register-with-eureka: true
# 是否从Eureka Server中抓取已有的注册信息,默认为true,单节点无所谓,集群必须设置为true才能配合Ribbon负载均衡
fetch-registry: true
service-url:
# defaultZone: http://localhost:7001/eureka/ 单机
defaultZone: http://eureka7002.com:7002/eureka/
instance:
instance-id: payment8002
prefer-ip-address: true # 展示ip
# Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认30秒)
lease-renewal-interval-in-seconds: 1
# Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认90秒),超时Eureka Server将剔除该服务
lease-expiration-duration-in-seconds: 2