SpringCloud系列 - Gateway 网关功能(五)
目录
一、介绍
二、创建项目
三、初体验 - 基本路由
3.1 规则配置
3.2 工作原理
(1)核心组件
(2)请求处理流程
(3)其他配置项:order
四、断言
4.1 路径(Path)断言
4.2 请求参数(Query)断言
4.3 自定义断言工厂🔥
(1)创建断言工厂类
(2)定义配置类
(3)重写apply方法
(4)重写shortcutFieldOrder方法(可选)
4.4 自定义断言工厂示例一
4.5 自定义断言工厂示例二
4.6 自定义断言工厂的注意事项
五、过滤器
5.1 路径重写(RewritePath)过滤🔥
5.2 添加响应头(AddResponseHeader) 过滤
5.3 内置过滤器清单
5.4 default-filters
5.5 Global Filter
(1)实现接口
(2)基本实现步骤
(3)示例代码
5.6 自定义过滤器工厂
(1)继承抽象基类
(2)定义配置类
(3)重写apply方法
(4)重写shortcutFieldOrder方法(可选)
(5)配置
5.7 自定义过滤器工厂示例
六、扩展 - 跨域处理
七、微服务之间的远程调用经过网关吗?
一、介绍
Gateway(网关)是微服务架构中的统一入口,负责接收所有客户端请求并转发至后端服务,同时集成路由、安全、监控等非业务功能。
其核心作用包括:
- 路由转发
根据请求路径(如/user/**
)、Header、参数等动态匹配目标服务,例如将/api/user
转发至用户服务user-service
。 - 负载均衡
集成服务发现(如Nacos、Eureka),通过轮询、随机等策略分发请求至多个服务实例。 - 安全控制
统一处理认证(JWT、OAuth2)、鉴权、IP白名单等,避免重复实现。 - 流量治理
支持限流(令牌桶算法)、熔断(如集成Sentinel)、降级等,保护后端服务。 - 协议转换
处理HTTP/gRPC/WebSocket等协议转换,适配异构系统。
🔥学习网关的核心:掌握网关相关的配置!!!
二、创建项目
我们单独创建一个模块,就叫gateway,简单明了。
pom文件
<dependencies><!-- 网关 --><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><!-- 配置中心 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!-- 负载均衡 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency></dependencies>
配置文件
server:port: 80
spring:application:name: gatewayprofiles:active: devcloud:nacos:server-addr: 127.0.0.1:8848discovery:namespace: ${spring.profiles.active:public}config:namespace: ${spring.profiles.active:public}---
spring:config:import:- nacos:common.yaml?group=gatewayactivate:on-profile: dev---
spring:config:import:- nacos:common.yaml?group=gatewayactivate:on-profile: test---
spring:config:import:- nacos:common.yaml?group=gatewayactivate:on-profile: prod
创建主程序
@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}
}
启动项目
三、初体验 - 基本路由
3.1 规则配置
值得注意的是
使用断言为各服务配置不同请求前缀后,别忘了在各个服务的请求前面一定要加上该请求前缀。具体的可以通过请求注解加上,也可以通过配置文件统一上下文配置好。
另外不要忘记远程调用客户端中的请求路径也要加上对应的前缀。
3.2 工作原理
(1)核心组件
Route(路由):定义ID、目标URI、断言(Predicate)和过滤器(Filter)。
Predicate(断言):匹配请求条件(如Path=/api/**
),决定是否路由。
Filter(过滤器):在请求前后执行逻辑(如修改Header、限流)。
spring:cloud:gateway:routes:- id: edu-route # 路由的id,没有固定规则但要求唯一,建议与服务名对应uri: lb://service-edu # lb 是负载均衡转发的意思predicates: # 断言,路径相匹配的进行路由- Path=/edu/** # 将/edu/**路径转发到service-edu- id: system-routeuri: lb://service-systempredicates:- Path=/system/**
(2)请求处理流程
- 客户端请求到达Gateway,由 DispatcherHandler 接收。
- RoutePredicateHandlerMapping 根据断言匹配路由。
- FilteringWebHandler 执行过滤器链(Pre→代理请求→Post)。
- 通过Netty Client转发请求至目标服务,返回响应。
(3)其他配置项:order
顺序优先级,不写默认排在前面的优先。
这在路由规则冲突的情况下,使用起来很明显。
举个例子:
如果我们把这个百度的/**路径放在最前面,那么再访问http://localhost/edu/test/getRemoteFeign路径就找不到了。
于是,使用上order的作用就出来了。不管谁写在前面谁写在后面,顺序都不重要了,优先级是按照order的数值决定,数值越小优先级越高。
四、断言
Route Predicate Factories :: Spring Cloud Gateway
名 | 参数(个数/类型) | 作用 |
After | 1/datetime | 在指定时间之后 |
Before | 1/datetime | 在指定时间之前 |
Between | 2/datetime | 在指定时间区间内 |
Cookie | 2/string,regexp | 包含cookie名且必须匹配指定值 |
Header | 2/string,regexp | 包含请求头且必须匹配指定值 |
Host | N/string | 请求host必须指定枚举值 |
Method | N/string | 请求方式必须指定枚举值 |
Path | 2/List<String>, bool | 请求路径满足规则,是否匹配最后的 / |
Query | 2/string, regexp | 包含指定请求参数 |
RemoteAddr | 1/List<String> | 请求来源于指定网络域(CIDR写法) |
Weight | 2/string, int | 按指定权重负载均衡 |
XForwardedRemoteAddr | 1/List<String> | 从X-Forwarded-For请求头中解析请求来源,并判断是否来源于指定网络域 |
4.1 路径(Path)断言
短写法
predicates:- Path=/edu/**
全写法
predicates:- name: Pathargs:pattern: /edu/**
4.2 请求参数(Query)断言
源码中显示可以传入param和regexp两个参数
现在就变成了必须路径带/s并且带参数名wd并且参数值必须是gateway。才允许跳转!
4.3 自定义断言工厂🔥
断言工厂是Spring Cloud Gateway中用于定义路由匹配条件的组件。当一个请求到达网关时,网关会检查请求是否满足路由规则中定义的一个或多个断言条件,只有全部满足时才会将请求路由到目标服务。
断言可以基于请求的各种属性进行匹配,如路径、方法、头部、参数等。Spring Cloud Gateway内置了多种断言工厂,如Path、Method、Header、Cookie等。但有时这些内置断言无法满足特殊业务需求,这时就需要自定义断言工厂。
(1)创建断言工厂类
自定义断言工厂需要继承AbstractRoutePredicateFactory抽象类,并且遵循特定的命名规范:类名必须以RoutePredicateFactory结尾。
@Component
public class CustomRoutePredicateFactory extends AbstractRoutePredicateFactory<CustomRoutePredicateFactory.Config> {public CustomRoutePredicateFactory() {super(Config.class);}// 其他必要方法...
}
(2)定义配置类
在断言工厂类内部定义一个静态内部类作为配置类,用于接收和存储断言所需的参数。
@Data
@NoArgsConstructor
public static class Config {private String param;private String value;// 其他配置参数...
}
(3)重写apply方法
apply方法是断言工厂的核心,在这里编写自定义的逻辑来判断请求是否满足断言条件。
@Override
public Predicate<ServerWebExchange> apply(Config config) {return exchange -> {// 自定义判断逻辑// 通过exchange对象可以获取请求的各种信息return true; // 返回是否匹配};
}
(4)重写shortcutFieldOrder方法(可选)
如果希望自定义断言支持快捷配置(在配置文件中使用简写形式),可以重写shortcutFieldOrder方法。
@Override
public List<String> shortcutFieldOrder() {return Arrays.asList("params", "value");
}
4.4 自定义断言工厂示例一
实现一个根据时间范围进行路由的断言工厂,例如只在9:00-18:00允许访问。
/*** 自定义:根据时间范围进行路由的断言工厂* @since 2025/7/8 10:15* @author Mr.Hongtao*/
@Component
public class HourRoutePredicateFactory extends AbstractRoutePredicateFactory<HourRoutePredicateFactory.Config> {/*** 无参构造器,继承父类传入当前的Config类*/public HourRoutePredicateFactory() {super(Config.class);}/*** 自定义断言支持快捷配置(在配置文件中使用简写形式),定义短写法的字段顺序* <p>可选的</p>*/@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("startHour", "endHour");}/*** 实现断言逻辑* @param config 配置类* @return 断言*/@Overridepublic Predicate<ServerWebExchange> apply(Config config) {return exchange -> {LocalDateTime now = LocalDateTime.now();int hour = now.getHour();return hour >= config.getStartHour() && hour <= config.getEndHour();};}/*** 内部类:定义可以配置的参数*/@Data@NoArgsConstructorpublic static class Config {private int startHour;private int endHour;}
}
配置使用
spring:cloud:gateway:routes:- id: three-pageuri: https://www.baidu.com/predicates:- name: Pathargs:pattern: /**- name: Hourargs:start-hour: 9end-hour: 18
测试
我们试试把本地时间调整到这个时段外,然后就发现找不到页面了。(测试完记得改回去)
4.5 自定义断言工厂示例二
实现一个根据请求参数中的年龄值进行路由的断言工厂
@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {public AgeRoutePredicateFactory() {super(Config.class);}@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("minAge", "maxAge");}@Overridepublic Predicate<ServerWebExchange> apply(Config config) {return exchange -> {String ageStr = exchange.getRequest().getQueryParams().getFirst("age");if (StringUtils.isNotEmpty(ageStr)) {int age = Integer.parseInt(ageStr);return age > config.getMinAge() && age < config.getMaxAge();}return false;};}@Data@NoArgsConstructorpublic static class Config {private int minAge;private int maxAge;}
}
4.6 自定义断言工厂的注意事项
注意事项 | 说明 |
命名规范 | 类名必须以RoutePredicateFactory结尾,Spring会根据配置中的断言名称自动匹配对应的工厂类。 |
组件扫描 | 确保自定义断言工厂类被Spring组件扫描到,通常使用@Component 注解。 |
配置顺序 | shortcutFieldOrder方法返回的参数顺序必须与配置文件中的参数顺序一致。 |
线程安全 | 断言工厂应该是无状态的,所有的配置应该通过Config类传递。 |
性能考虑 | 断言逻辑应尽量简单高效,因为每个请求都会执行这些判断。 |
五、过滤器
过滤器可以在请求路由到目标服务之前或之后对请求和响应进行修改、验证或记录,是实现鉴权、限流、日志、请求改写等功能的核心机制。
过滤器执行流程大致如下:
- 客户端请求
- Pre 过滤器处理
- 路由转发
- 目标服务处理
- Post 过滤器处理
- 返回响应
以下是官网关于过滤器的文档截图:
5.1 路径重写(RewritePath)过滤🔥
关于过滤器,这里演示一种RewritePath,这是网关经常会用到的,比较多的一种过滤规则。
前面我们说:如果要让网关统一管理不同的微服务,就需要为每个微服务的所有接口都加上各自的路径前缀,也就是说每个请求注解都要加前缀比如/api/order,包括说远程调用客户端也同样要。
这样网关在转发的时候,就能直接到达目的地。
但是这样操作是非常麻烦的,几乎每个接口都要写一遍!
现在提出一种更便捷的办法,就是路径重写。
这样我们各个业务模块代码中没有添加路径前缀。但是在网关中统一为各模块配置路径前缀,页面在访问的时候,网关就可以按照过滤器的路径重写规则,找到真实的后端服务地址。
5.2 添加响应头(AddResponseHeader) 过滤
- AddResponseHeader=响应头参数名,参数对应的值
5.3 内置过滤器清单
当然过滤器还有很多,这里就不一一演示了,如下列举了几种过滤器,供大家学习参考:
名 | 参数(个数/类型) | 作用 |
AddRequestHeader | 2/string | 添加请求头 |
AddRequestHeadersIfNotPresent | 1/List<String> | 如果没有则添加请求头, key:value方式 |
AddRequestParameter | 2/string, string | 添加请求参数 |
AddResponseHeader | 2/string, string | 添加响应头 |
SetStatus | 1/int | 设置响应状态码 |
Retry | 7/string | 请求重试设置 |
RequestSize | 1/string | 请求大小限定 |
RewritePath | 2/string | 路径重写 |
RewriteRequestParameter | 2/string | 请求参数重写 |
RewriteResponseHeader | 3/string | 响应头重写 |
5.4 default-filters
我们如果想给每个服务都使用相同的过滤器,除了在每个服务的routes下都单独配置外,还可以进行统一配置:default-filters,位置与routes是同一级。
spring:gateway:default-filters:- AddResponseHeader=X-Response-Header, Hssy10
5.5 Global Filter
全局过滤器是 Spring Cloud Gateway 提供的一种特殊过滤器,它能够拦截并处理所有的 HTTP 请求和响应。与 GatewayFilter 通过配置定义且处理逻辑固定不同,GlobalFilter 的逻辑需要开发者自己编写代码实现。
(1)实现接口
全局过滤器需要实现 GlobalFilter 接口,该接口只有一个核心方法:
public interface GlobalFilter {Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
exchange
:请求上下文,可以获取 Request、Response 等信息chain
:用来把请求委托给下一个过滤器
(2)基本实现步骤
- 创建类实现 GlobalFilter接口
- 添加 @Component 注解使其成为 Spring Bean
- 通过 @Order 注解或实现 Ordered接口指定执行顺序
- 在 filter方法中编写业务逻辑
(3)示例代码
基础认证过滤器
@Component
@Order(-1) // 高优先级
public class AuthGlobalFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String token = exchange.getRequest().getHeaders().getFirst("Authorization");if (!"valid-token".equals(token)) {// 认证失败exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();}// 认证通过,继续执行过滤器链return chain.filter(exchange);}
}
疑问:为什么响应头中没有携带上配置文件中定义的响应过滤器?
由于这个全局过滤器直接response响应了,所以配置文件中我们添加的过滤器比如响应头,这些就没有生效了。
带上Authorization请求头看看效果
为了方便演示,我们给edu服务添加一个请求头过滤器看看效果(真实环境肯定不是直接在这配)
说明我们配置的这个全局过滤器生效了。
5.6 自定义过滤器工厂
前面列举了内置的过滤器清单,但是这些过滤器并不能完全满足所有需求。
为了满足有些特殊的需求,于是我们可以通过编写代码来构造一个全局过滤器。
这种方式也可以弥补一定的不足。不过它是全局的,意思是全部模块都要生效的。
下面介绍一种自定义过滤器工厂的方式,这种方式可以像自定义断言工厂一样使用。
即可以用在default-filters上针对全部路由,也可以作用在某个模块上过滤。
(1)继承抽象基类
推荐继承 AbstractGatewayFilterFactory,这是Spring提供的基类,简化了开发流程。类名必须以 GatewayFilterFactory结尾,配置中使用前缀部分(如Custom
)
@Component
public class CustomFilterFactory extends AbstractGatewayFilterFactory<CustomFilterFactory.Config> {// 配置类定义public static class Config {private String param1;private int param2;// getters/setters省略}public CustomFilterFactory() {super(Config.class); // 必须调用父类构造函数}@Overridepublic GatewayFilter apply(Config config) {return (exchange, chain) -> {// 过滤逻辑实现System.out.println("参数1: " + config.param1);return chain.filter(exchange);};}
}
(2)定义配置类
public static class Config {private String key;private String value;// getters/setters
}
(3)重写apply方法
@Overridepublic GatewayFilter apply(Config config) {return (exchange, chain) -> {// 过滤逻辑实现System.out.println("参数1: " + config.param1);return chain.filter(exchange);};}
(4)重写shortcutFieldOrder方法(可选)
@Override
public List<String> shortcutFieldOrder() {return Arrays.asList("key", "value"); // 对应YAML中的参数顺序
}
(5)配置
filters:- name: Custom # 自动匹配CustomGatewayFilterFactoryargs:key: X-Custom-Headervalue: GatewayValue
怎么样,是不是发现和自定义断言工厂的方法很像?没错!这里就不一一进行演示了。
5.7 自定义过滤器工厂示例
@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.Config> {public AuthGatewayFilterFactory() {super(Config.class);}@Overridepublic GatewayFilter apply(Config config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {return chain.filter(exchange).then(Mono.fromRunnable(() -> {// 每次响应前,添加一个一次性令牌,支持uuid、jwt等各种方式ServerHttpResponse response = exchange.getResponse();HttpHeaders headers = response.getHeaders();String value = config.getValue();if ("uuid".equals(value)) {value = UUID.randomUUID().toString();} else if ("jwt".equals(value)) {// 模拟一串jwtvalue = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI" +"6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";}headers.add(config.getParam(), value);}));}};}@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("param", "value");}@Datapublic static class Config {private String param;private String value;}
}
测试
六、扩展 - 跨域处理
至此,我们关于gateway的用法其实基本就结束了。
这里扩展一下常见的通过网关处理跨域问题。
以前,我们每个服务可以通过@CrossOrigin或者配置类来解决跨域问题,但是现在我们是微服务,涉及到的服务众多,每个微服务都按以前的方式来搞,也不是不可以。只是个人决定稍微有点麻烦,这里就用网关的方式,统一处理请求跨域。
具体的可以参考官网:CORS Configuration :: Spring Cloud Gateway
七、微服务之间的远程调用经过网关吗?
可以经过网关,也可以不用经过。
比如我们把远程调用的服务名称写成网关的服务名称,然后远程调用的请求路径写网关配置的前缀+路径。这样,远程调用就会走网关。否则远程调用是不经过网关的。
但是,没有必要!
因为网关是直接对接前端的,后端还是老老实实通过各个服务进行调用。