Spring Cloud是Spring旗下的项目之一
Spring Cloud并不是一个组件 而是许多组件的集合
其将当下非常流行的一些技术整合到了一起 实现了多个分布式开发中的重要功能
协调了分布式环境中各个系统 并且为各类服务提供模板性的配置
其主要涉及的组件包括:
【在本篇中 将介绍Spring Cloud Gateway网关】
在多个微服务之间为了避免直接暴露地址 增强服务的安全性 需要有一个网关 用于将客户的请求转发到不同的微服务
Spring Cloud Gateway是Spring基于Spring 5.0、 Spring Boot 2.0、Project Reactor等技术开发的网关服务 基于了Filter链提供网关基本功能:安全 监控/埋点 限流等 为微服务架构提供了简单 有效且统一的API路由管理方式
Spring Cloud Gateway是替代Netflix Zuul的一套解决方案 该组件的核心是一系列的过滤器 通过这些过滤器可以将客户端发送的请求转发(也称路由)到对应的微服务
Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器 通过隐藏微服务节点的IP端口信息从而加强安全保护
其本身也是一个微服务 因此 需要注册到Eureka服务注册中心
网关的核心功能是:过滤和路由
不管是来自于PC端或移动端的请求 还是服务间的内部调用 一切对服务的请求都先经过网关 然后再由网关来实现鉴权 动态路由等操作
Gateway就是服务的统一入口 相当于一个大门
添加Spring Cloud Gateway的依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
然后在启动类中用@EnableDiscoveryClient
注解将其注册到Eureka客户端:
@SpringBootApplication
// 开启Eureka客户端的发现功能
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
在配置文件application.yml中配置路由规则:
server:
port: 10010
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
# 路由id 可任意写
- id: user-service-route
# 代理的服务地址(注意:是uri不是url)
# uri: http://127.0.0.1:9091
# 路由断言 匹配映射路径的条件(注意:Path的P要大写)
predicates:
- Path=/user/**
当然 虽然可以直接写服务地址 更推荐面向服务的路由
一个原因是地址写死不方便后序修改维护 而且面向服务的路由可以自动进行Ribbon负载均衡
只需要将ip和端口改为服务名即可(当然 这个服务必须首先得存在)
就像这样:
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
# 路由id 可任意写
- id: user-service-route
# 代理的服务名(注意:是uri不是url)
# (lb:LoadBalancer负载均衡 底层使用Ribbon进行负载均衡)
uri: lb://user-service
# 路由断言 匹配映射路径的条件(注意:Path的P要大写)
predicates:
- Path=/user/**
需要注意的是 前面要加上lb(LoadBalancer)
当路由配置中uri所用的协议为lb时 Gateway将自动使用LoadBalancerClient将后面的服务名通过Eureka解析为实际的主机和端口 并进行Ribbon负载均衡
当客户端访问的请求地址和微服务的服务地址不一致的时候 可以通过配置路径过滤器来实现映射路径中地址的添加或去除
通过配置路由的过滤器(filters)的PrefixPath属性实现:
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/**
filters:
# 添加请求路径的前缀(prefixpath)
# 若要添加多个只需继续添加即可 例如PrefixPath=/user/aa/bb/cc
- PrefixPath=/user
用StripPrefix属性指定在路由路径中要去除的前缀的个数:
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/aa/bb/cc/user/**
filters:
# 去除的路径前缀的个数(strip)
- StripPrefix=3
Gateway作为网关的其中一个重要功能就是实现请求的鉴权 而这个操作往往是通过网关提供的过滤器来实现的
常见过滤器:
过滤器名称 | 功能 |
---|---|
AddRequestHeader | 对匹配上的请求添加Header |
AddRequestParameters | 对匹配上的请求的路由添加参数 |
AddResponseHeader | 对从网关返回的响应添加Header |
PrefixPath | 对匹配上的请求路径添加前缀 |
PrefixPath | 对匹配上的请求路径去除前缀 |
使用的时候 直接在配置文件中的default-filters进行配置即可:
default-filters是默认过滤器 也就是全局过滤器 会对所有的路由都生效 而不仅仅只针对某个路由
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/user/**
# 默认过滤器 对所有的路由都生效 而不仅仅只针对某个路由
default-filters:
# 响应头过滤器
# 对输出的响应设置其头部属性名称为X-Response-MyName 值为piconjo 逗号分隔属性名和属性值
# 若有多个参数则重写一行设置不同的参数
- AddResponseHeader=X-Response-MyName,piconjo
- AddResponseHeader=X-Response-MyLocation,zhejiang
Gateway在实现方式上分为两种过滤器:
Spring Cloud Gateway的Filter的生命周期类似于Spring MVC的拦截器
共有两个 分别是pre和post
pre和post分别会在请求被执行前调用和被执行后调用
pre和post可以通过过滤器的GatewayFilterChain执行filter方法来实现
常见应用场景:
首先在配置文件中配置过滤器类的类名和要过滤的属性名:
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/user/**
filters:
# 自定义的过滤器 属性名就是过滤器类的类名的GatewayFilterFactory前面的部分 属性值限定了要过滤的参数名为name
- MyParam=name
在上面的案例中 已经指定了自定义的过滤器类的类名前面为MyParam 那么后面固定的是GatewayFilterFactory 必须是这种格式 否则会报错
在过滤器类MyParamGatewayFilterFactory.java中编写过滤逻辑:
有几个关键点需要注意的是:
@Component
注解MyParamGatewayFilterFactory()
方法调用父类的方法 其值就是内部类的classshortcutFieldOrder()
方法返回的是参数列表// 过滤器需要成为Spring的组件
@Component
// 需要继承AbstractGatewayFilterFactory 然后以内部类作为泛型
public class MyParamGatewayFilterFactory extends AbstractGatewayFilterFactory<MyParamGatewayFilterFactory.Config> {
// 属性值必须与内部类中的参数的名称一致
static final String PARAM_NAME="param";
public MyParamGatewayFilterFactory() {
super(Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList(PARAM_NAME);
}
// 内部类
public static class Config
{
// 对应的是在配置过滤器的时候所指定的参数名(这个参数名可以是name也可以是age等等) 是随意取的
private String param;
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求
ServerHttpRequest request = exchange.getRequest();
// 判断是否包含指定的参数名(即自己写的内部类里配置的参数名param)
// 若包含 则进一步操作 注:这里的config.param就相当于设置的请求参数名 可以是name也可以是age等等
if (request.getQueryParams().containsKey(config.param))
{
// 根据属性名获取对应的属性值并且输出
request.getQueryParams().get(config.param)
.forEach(value -> System.out.println("自定义的局部过滤器已获取到属性名:"+config.param+",属性值:"+value));
}
return chain.filter(exchange);
}
};
}
}
配置全局过滤器比较方便 直接写一个过滤器即可
这个自定义的全局过滤器必须实现GlobalFilter接口
若要修改多个过滤器的执行顺序的话 还可实现Ordered接口
直接在filter()
方法内写过滤器的逻辑即可
// 过滤器需要成为Spring的组件
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
// 过滤器逻辑
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("全局过滤器执行...");
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (StringUtils.isBlank(token))
{
// 设置状态码为未授权
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 设为已完成 不再继续执行
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
// 过滤器执行顺序 return的值越小越先执行
@Override
public int getOrder() {
return 1;
}
}
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000
ribbon:
ConnectTimeout: 1000
ReadTimeout: 2000
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 0
跨域:在来自前端的请求访问中 若访问的地址与当前服务器的域名 ip或端口号不一致 则称为跨域请求
若不解决则不能获取到对应地址的返回结果
同样在配置文件中进行配置即可:
spring:
cloud:
gateway:
globalcors:
corsConfigurations:
# '[/**]' 表示对于所有访问到网关服务器的请求地址
'[/**]':
# 允许的源站:* # 或是下面的写法都可以 *表示全部
allowedOrigins:
- "http://www.baidu.com"
- "http://localhost:8080"
# 允许的请求方式
allowedMethods:
- GET