看完就会的Spring Cloud Gateway
在前面几节,我给大家介绍了当一个系统拆分成微服务后,会产生的问题与解决方案:服务如何发现与管理(Nacos注册中心实战),服务与服务如何通信(Ribbon, Feign实战)
今天我们就来聊一聊另一个问题:客户端如何访问?
在单体架构时,我们的系统只有一个入口,前端人员调用起来十分的简单。
但是当我们拆分为一个微服务系统后,每个服务都有属于自己ip和端口号,我们不可能跟前端说:诶,调用这个接口的时候你就使用这个地址哈。
前端:

既然这样不行的话,那我们能不能利用已有的知识想一个解决方案呢?
不是真的能用的解决方案
其实我们很容易的就能想到,我们的服务是具备互相发现及通信的能力的,那么,我们是不是可以搞一个类似统一入口(网关)样的服务,前端只请求这个服务,由这个服务去调用真实服务的Feign接口。
举个例子:
- 商品服务的获取商品接口:localhost:8080/get/goods
- 订单服务的下订单接口:localhost:8081/order
现在有个网关服务, 里面有两个接口:localhost:5555/get/goods, localhost:5555/order
前端调用获取商品接口时,访问:localhost:5555/get/goods,然后网关服务调用商品服务的Feign接口
下单时:访问:localhost:5555/order,然后网关服务调用订单服务的Feign接口
小结一下:
这个方案是否解决了服务入口统一的问题:解决了
能用吗:能用,但不是完全能用
因为这样会有一个问题,服务写的每一个接口,都需要给出一个Feign接口,给我们的网关服务调用。
真正的解决方案
Spring Cloud为我们提供了一个解决方案:Spring Cloud Gateway
Spring Cloud Gateway提供了一个建立在Spring生态系统之上的API网关,能够简单而有效的方式来路由到API,并基于 Filter 的方式提供一些功能,如:安全、监控。
Spring Cloud Gateway是由Spring Boot 2.x、Spring WebFlux和Reactor实现的,需要Spring Boot和Spring Webflux提供的Netty运行环境。它不能在传统的Servlet容器中工作,也不能在以WAR形式构建时工作。
官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/
概念
Route(路由):网关的基本构件,它由一个ID、一个目的地URI、一个断言集合和一个过滤器集合定义。如果集合断言为真,则路由被匹配。
Predicate(断言):Java 8断言函数。参数类型是Spring Framework ServerWebExchange。可以让开发者在HTTP请求中的任何内容上进行匹配,比如头文件或参数。
Filter(过滤):由特定的工厂构建的GatewayFilter的实例,与传统的Filter一样,能够请求前后对请求就行处理。
工作原理

客户端向Spring Cloud Gateway发出请求。如果Gateway处理程序映射确定一个请求与路由相匹配,它将被发送到Gateway Web处理程序。这个处理程序通过一个特定于该请求的过滤器链来运行该请求。
过滤器可以在代理请求发送之前和之后运行pre和post逻辑。
简单使用
准备
预先准备一个服务,用来测试路由
我这里准备了个一个商品服务,并提供了一个接口:http://localhost:8082/goods/get-goods

现在,开始编写网关服务
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
编写配置
bootstrap.yaml
server:
port: 5555
spring:
application:
name: my-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: public
username: nacos
password: nacos
logging:
level:
org.springframework.cloud.gateway: info
com.alibaba.nacos.client.naming: warn
application.yaml
spring:
cloud:
gateway:
# 路由配置
routes:
# 路由id, 保证唯一性
- id: my-goods
# 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名
uri: lb://my-goods
# 断言
predicates:
# 匹配goods开头的请求
- Path=/goods/**
启动类
package com.my.micro.service.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author Zijian Liao
* @since 1.0.0
*/
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
测试
启动服务,并访问:http://localhost:5555/goods/get-goods

可以看到,服务成功被路由了
一个简单的网关服务就这样完成了,小伙伴看完过有没有对网关的概念更加深刻呢?
断言
在上面的例子中,我们就用到了一个断言工厂:Path
在Spring Cloud Gateway中,所有的断言工厂都是继承于AbstractRoutePredicateFactory, 并且命名规则为:XxxRoutePredicateFactory, 比如Path的类名为:PathRoutePredicateFactory
那么,Spring Cloud Gateway给我们内置了哪些断言工厂呢?
以下展示我觉得常用的断言工厂,更多的内容还请小伙伴自己查看文档
After
匹配在某个时间(ZonedDateTime)后的请求
spring:
cloud:
gateway:
# 路由配置
routes:
# 路由id, 保证唯一性
- id: my-goods
# 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名
uri: lb://my-goods
# 断言
predicates:
# 匹配goods开头的请求
- Path=/goods/**
# 匹配23:05分后的请求
- After=2021-08-08T23:05:13.605+08:00[Asia/Shanghai]
我们在23:03进行测试

访问失败了
Before
匹配在某个时间(ZonedDateTime)前的请求
与After相似,不再演示
Between
匹配在某个时间段(ZonedDateTime)的请求
spring:
cloud:
gateway:
# 路由配置
routes:
# 路由id, 保证唯一性
- id: my-goods
# 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名
uri: lb://my-goods
# 断言
predicates:
# 匹配goods开头的请求
- Path=/goods/**
# 匹配23:05-23:10的请求
- Between=2021-08-08T23:05:13.605+08:00[Asia/Shanghai],2021-08-08T23:10:13.605+08:00[Asia/Shanghai]
Host
匹配某个Host的请求
spring:
cloud:
gateway:
# 路由配置
routes:
# 路由id, 保证唯一性
- id: my-goods
# 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名
uri: lb://my-goods
# 断言
predicates:
# 匹配goods开头的请求
- Path=/goods/**
#配置host为192.168.1.105请求
- Host=192.168.1.105
注意,测试时需要将端口号改为80
尝试使用127.0.0.1发起调用

改为192.168.1.105进行调用

RemoteAddr
匹配指定的远程源地址
spring:
cloud:
gateway:
# 路由配置
routes:
# 路由id, 保证唯一性
- id: my-goods
# 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名
uri: lb://my-goods
# 断言
predicates:
# 匹配goods开头的请求
- Path=/goods/**
#配置RemoteAddr为192.168.1网段的地址
- RemoteAddr=192.168.1.1/24
测试

启用内网穿透测试

访问失败了
过滤器
关于过滤器这块我举个例子,更多的内容请小伙伴自己查阅文档
官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
举一个用的比较多的过滤器:
StripPrefix
顾名思义,除去前缀的过滤器,将匹配的请求的前缀去除,将去除后的请求转发给下游服务
spring:
cloud:
gateway:
# 路由配置
routes:
# 路由id, 保证唯一性
- id: my-goods
# 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名
uri: lb://my-goods
# 断言
predicates:
# 匹配goods开头的请求
- Path=/api/goods/**
filters:
# 1表示去除一个前缀
- StripPrefix=1
组合来看,意思是当客户端发起请求:http://localhost:5555/api/goods/get-goods, 匹配该路由,然后将第一个前缀
api去除,然后转发给商品服务,转发的路径为:/goods/get-goods
测试

自定义断言工厂
上面提到过:所有的断言工厂都是继承于AbstractRoutePredicateFactory, 并且命名规则为:XxxRoutePredicateFactory, 比如Path的类名为:PathRoutePredicateFactory
我们现在就来尝试实现一个自定义的请求头断言工厂吧
编写代码
package com.my.micro.service.gateway.filter;
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 java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
/**
* @author Zijian Liao
* @since 1.0.0
*/
@Component
public class MyHeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<MyHeaderRoutePredicateFactory.Config> {
/**
* Header key.
*/
public static final String HEADER_KEY = "header";
/**
* Regexp key.
*/
public static final String REGEXP_KEY = "regexp";
public MyHeaderRoutePredicateFactory() {
super(MyHeaderRoutePredicateFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(HEADER_KEY, REGEXP_KEY);
}
@Override
public Predicate<ServerWebExchange> apply(MyHeaderRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
// 获取请求头
List<String> values = exchange.getRequest().getHeaders()
.getOrDefault(config.header, Collections.emptyList());
if (values.isEmpty()) {
return false;
}
// 判断请求头中的值是否与配置匹配
return values.stream()
.anyMatch(value -> value.matches(config.regexp));
}
@Override
public String toString() {
return String.format("Header: %s=%s ", config.header, config.regexp);
}
};
}
public static class Config {
private String header;
private String regexp;
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
public String getRegexp() {
return regexp;
}
public void setRegexp(String regexp) {
this.regexp = regexp;
}
}
}
编写配置
spring:
cloud:
gateway:
# 路由配置
routes:
# 路由id, 保证唯一性
- id: my-goods
# 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名
uri: lb://my-goods
# 断言
predicates:
# 匹配goods开头的请求
- Path=/api/goods/**
# 匹配header为name=aljian的请求
- MyHeader=name,ajian
filters:
# 1表示去除一个前缀
- StripPrefix=1
测试
直接在浏览器中访问

改用postman访问

自定义过滤器
自定义过滤器的方式与自定义断言工厂的方式大致相同,所以过滤器继承于AbstractGatewayFilterFactory或者AbstractNameValueGatewayFilterFactory, 命名规则为XxxGatewayFilterFactory
比如内置的添加请求头过滤器
public class AddRequestHeaderGatewayFilterFactory
extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
// 获取到需要添加的header value
String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
// 将header添加到request中
ServerHttpRequest request = exchange.getRequest().mutate()
.header(config.getName(), value).build();
// 重新构建出一个exchange
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public String toString() {
return filterToStringCreator(AddRequestHeaderGatewayFilterFactory.this)
.append(config.getName(), config.getValue()).toString();
}
};
}
}
全局过滤器
以上内容都是针对于每一个router,Spring Cloud Gateway提供了一个针对所有router的全局过滤器
实现方式如下
package com.my.micro.service.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author Zijian Liao
* @since 1.0.0
*/
@Slf4j
@Component
public class MyGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
log.info("进入全局过滤器,请求路径为:{}", path);
// 编写任何你想要实现的逻辑,比如权限校验
return chain.filter(exchange);
}
}
测试

自定义异常处理器
小伙伴应该发现了,在遇到错误时,Spring Cloud Gateway返回给客户端的异常并不优雅,所以我们需要自定义异常处理
编写自定义异常处理器
package com.my.micro.service.gateway.exception;
import com.my.micro.service.gateway.result.BaseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
/**
* @author Zijian Liao
*/
@Slf4j
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
/**
* Create a new {@code DefaultErrorWebExceptionHandler} instance.
*
* @param errorAttributes the error attributes
* @param resourceProperties the resources configuration properties
* @param errorProperties the error configuration properties
* @param applicationContext the current application context
*/
public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
@NonNull
@Override
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Throwable throwable = getError(request);
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(this.handleError(throwable)));
}
private BaseResult<Void> handleError(Throwable throwable){
return BaseResult.failure(throwable.getMessage());
}
}
BaseResult
package com.my.micro.service.gateway.result;
import lombok.Data;
/**
* @author Zijian Liao
* @since 1.0.0
*/
@Data
public class BaseResult<T> {
private Integer code;
private String message;
public BaseResult(Integer code, String message){
this.code = code;
this.message = message;
}
public static <T> BaseResult<T> failure(String message){
return new BaseResult<>(-1, message);
}
}
编写配置类
package com.my.micro.service.gateway.exception;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import java.util.stream.Collectors;
/**
* @author Zijian Liao
* @since 1.0.0
*/
@Configuration
public class ExceptionConfiguration {
@Primary
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes, ServerProperties serverProperties, ResourceProperties resourceProperties,
ObjectProvider<ViewResolver> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer,
ApplicationContext applicationContext) {
DefaultErrorWebExceptionHandler exceptionHandler = new JsonExceptionHandler(errorAttributes,
resourceProperties, serverProperties.getError(), applicationContext);
exceptionHandler.setViewResolvers(viewResolversProvider.orderedStream().collect(Collectors.toList()));
exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
return exceptionHandler;
}
}
测试

小结
本编介绍了关于微服务架构中——客户端如何访问的解决方案:Spring Cloud Gateway
其中介绍了Gateway的三个核心概念:Route,Predicate,Filter。并演示了如何配置及使用他们,还讲解了如何自定义Predicate和Filter。
最后介绍了Spring Cloud Gateway的全局过滤器,以及如何实现自定义异常处理。
以上
看完之后想必有所收获吧~ 想要了解更多精彩内容,欢迎关注公众号:程序员阿鉴,阿鉴在公众号欢迎你的到来~
个人博客空间:https://zijiancode.cn/archives/gateway
gittee: https://gitee.com/lzj960515/my-micro-service-demo
看完就会的Spring Cloud Gateway的更多相关文章
- Spring Cloud Gateway服务网关
原文:https://www.cnblogs.com/ityouknow/p/10141740.html Spring 官方最终还是按捺不住推出了自己的网关组件:Spring Cloud Gatewa ...
- 网关服务Spring Cloud Gateway(一)
Spring 官方最终还是按捺不住推出了自己的网关组件:Spring Cloud Gateway ,相比之前我们使用的 Zuul(1.x) 它有哪些优势呢?Zuul(1.x) 基于 Servlet,使 ...
- spring cloud gateway 之限流篇
转载请标明出处: https://www.fangzhipeng.com 本文出自方志朋的博客 在高并发的系统中,往往需要在系统中做限流,一方面是为了防止大量的请求使服务器过载,导致服务不可用,另一方 ...
- 跟我学SpringCloud | 第十二篇:Spring Cloud Gateway初探
SpringCloud系列教程 | 第十二篇:Spring Cloud Gateway初探 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如 ...
- Spring Cloud Gateway入坑记
Spring Cloud Gateway入坑记 前提 最近在做老系统的重构,重构完成后新系统中需要引入一个网关服务,作为新系统和老系统接口的适配和代理.之前,很多网关应用使用的是Spring-Clou ...
- spring cloud:服务网关 Spring Cloud GateWay 入门
Spring 官方最终还是按捺不住推出了自己的网关组件:Spring Cloud Gateway ,相比之前我们使用的 Zuul(1.x) 它有哪些优势呢?Zuul(1.x) 基于 Servlet,使 ...
- 快速突击 Spring Cloud Gateway
认识 Spring Cloud Gateway Spring Cloud Gateway 是一款基于 Spring 5,Project Reactor 以及 Spring Boot 2 构建的 API ...
- Spring Cloud Gateway 没有链路信息,我 TM 人傻了(中)
本系列是 我TM人傻了 系列第五期[捂脸],往期精彩回顾: 升级到Spring 5.3.x之后,GC次数急剧增加,我TM人傻了 这个大表走索引字段查询的 SQL 怎么就成全扫描了,我TM人傻了 获取异 ...
- Spring Cloud Gateway 雪崩了,我 TM 人傻了
本系列是 我TM人傻了 系列第六期[捂脸],往期精彩回顾: 升级到Spring 5.3.x之后,GC次数急剧增加,我TM人傻了 这个大表走索引字段查询的 SQL 怎么就成全扫描了,我TM人傻了 获取异 ...
随机推荐
- Duilib的双缓冲实现,附带GDI、WTL的双缓冲实现
前言: 闪烁问题,之前的经验是使用双缓冲,借此机会,把双缓冲的研究心得总结下. 双缓冲的含义: 缓冲这个词,相信大家都不陌生,Cache.主要是为了解决上下游(或者模块.或者系统)等性能不匹配问题.如 ...
- 温故知新,.Net Core利用UserAgent+rDNS双解析方案,正确识别并反爬虫/反垃圾邮件
背景 一般有价值的并保有数据的网站或接口很容易被爬虫,爬虫会占用大量的流量资源,接下来我们参考历史经验,探索如何在.Net Core中利用UserAgent+rDNS双解析方案来正确识别并且反爬虫. ...
- python之list列表(基础篇)
特点:1.有序的 2.可以存放多个元素 3.每个元素可以是任何数据类型,4,通过下标值访问1,定义一个空列表 2,定义一个非空列表 3.访问列表中的元素(同str类型) 4,切片与步长(同str类型 ...
- 【luogu P3807】【模板】卢卡斯定理/Lucas 定理(含 Lucas 定理证明)
[模板]卢卡斯定理/Lucas 定理 题目链接:luogu P3807 题目大意 求 C(n,n+m)%p 的值. p 保证是质数. 思路 Lucas 定理内容 对于非负整数 \(n\),\(m\), ...
- buu 新年快乐
一.查壳 发现是upx的壳. 二.拖入ida,发现要先脱壳. 题外话.总结一下手动脱壳,esp定律: 1.先单步到只有esp红色时,右键数据窗口跟随. 2.到数据窗口后,左键硬件访问,byte和wor ...
- 卧槽,原来不需要FQ就可以构建海外镜像
一. 背景 使用docker或者k8s的过程中,我们可能遇到镜像无法下载的情况,例如:kubernetes的kube-apiserver镜像,这是因为其仓库在海外,我们的网络被墙,我发获取到该资源,使 ...
- Luogu P4553 80人环游世界
link 题目大意 自东向西有 \(n\) 个国家.有 \(m\) 个人,他们可以选择 \(n\) 个国家中任意一个开始,任意一个结束,但路线必须自东向西,且第 \(i\) 个国家必须恰好经过 \(v ...
- ICMP、ARP协议介绍和ping命令
交换机工作原理和常用的简单命令 一.ICMP协议 1)ICMP协议的封装 二.ARP协议 1)什么是ARP协议 2)ARP相关命令 三.Ping命令的使 ...
- ubuntu18.04安装redis-desktop-manager
通过proxychains4 clone项目,否则安装不成功 教程:https://www.cnblogs.com/bignode/p/9254500.html 1 git clone --recur ...
- spring-1-spring介绍和IOC容器开发
一.介绍 1.版本 2.下载(jar包依赖) 下载 所以搜索:https://repo.spring.io/release/org/springframework/spring/ 文件分配 maven ...