⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 杨哲Tech实验室 「杨哲Tech实验室」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

网关Gateway

Netflix的Zuul一直不能很好地支持响应式编程,后来Spring Cloud推出自己的网关Gateway,Gateway和传统的组件不同,它依赖于Spring Boot和Spring WebFlux采用的是响应式编程(Reactive Programming)

新建一个Gateway模块,并引入如下依赖

<!-- 引入Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 引入服务发现 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

需要注意的几点:

  • Gateway用的是SpringWebFlux技术,它和spring-boot-starter-web的会产生冲突,因此只能引入其一
  • Gateway只支持Netty容器,引入Tomcat或者Jetty等同期,在运行期间会引发异常
  • 如果在新建模块或者项目的时候选择了war模式,则需要删除spring-boot-starter-web和IDE生成的ServletInitializer.java并将打包形式改为jar,否则会引发错误

通过代码配置路由

然后修改启动类,添加路由规则,代码如下

package com.scd.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
/**
* 创建路由规则
* @param builder -- 路由构造器
* @return 路由规则
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 配置路由
// route方法两个参数:第一个是id;第二个断言
.route("customer", r -> r.path("/customer-api/**")
// 过滤器,删除一个层级再匹配地址
.filters(f->f.stripPrefix(1))
// 转发到具体的URI
.uri("http://localhost:3001"))
// 基于服务发现的路由
.route("goods", r->r.path("/goods-api/**")
// 过滤器,删除一个层级再匹配地址
.filters(f->f.stripPrefix(1)
// 约定以“lb://{service-id}”为格式
.uri("lb://goods"))
// 创建
.build();
}
}

customRouteLocator方法会返回一个RouteLocator对象,这是Gateway的路由对象,将其装配到SpringIoC容器后,Gateway就会将其加载并作为路由规则

customRouteLocator方法中有两个路由规则,都是以.route()方法开头,每个route()方法代表一个路由,它有两个参数一个是字符串参数,作为路由id,另一个是断言,代码中是一个lambda表达式。

这里的断言作用是路由匹配,判断哪些请求和这个路由匹配,path方法表示采用路径匹配,当出现了与ANT风格表达式”/customer-api/**“匹配的路径时,就会启用这个路由;

接着是filters方法,它代表过滤器,也就是在Gateway执行路由之前,或者之后可以加入一些逻辑,这里的stripPrefix方法表示删除匹配地址中的一个层级,如果配置的匹配路径是/customer-api/**,那么请求地址是http://localhost:6001/customer-api/customer/name/1,通过Gateway的路由源服务器地址就是http://localhost:3001/customer-api/customer/name/1显然这是不能匹配到源服务器里的,而这里的stripPrefix方法代表删除一个层级,就是删除了路径中的/customer-api,这样路由到源服务器的地址就是http://localhost:3001/customer/name/1了,这才是能找到服务的地址;uri方法配置的则是源服务器的路径,是真实的源服务器地址

customRouteLocator方法中第二个route方法和第一个不同的是uri方法配置的内容,这里配置的是lb://goods,在Gateway中约定格式lb://{service-id}为服务发现的路由,通过它就可以将请求匹配到商品(Goods)服务的各个可用实例,从而实现负载均衡,在注册服务治理中心的环境中,经常会用到服务发现的路由方式

通过配置文件配置路由

除了这种编码路由的方式,Gateway还提供了配置的方法,在Gateway项目的application.yml文件写入如下配置,和上面的代码等价

# Spring应用(微服务)名称
spring:
application:
name: gateway
cloud:
gateway:
# 开始配置路径
routes:
# 路径匹配
- id: customer
# 转发URI
uri: http://localhost:3001
# 断言配置
predicates:
- Path=/customer-api/**
# 过滤器配置
filters:
- StripPrefix=1
# 路径匹配
- id: goods
# 转发URI
uri: lb://goods
# 断言配置
predicates:
- Path=/goods-api/**
# 过滤器配置
filters:
- StripPrefix=1
# 向端口为1001和1002的Eureka服务治理中心注册
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1001/eureka, http://localhost:1002/eureka
# 配置启动端口
server:
port: 6001
logging:
level:
root: info

路由断言工厂

Gateway中几个比较重要的概念:

  • 断言(Predicate): 断言用于检查请求是否与路由匹配,只有在相互匹配时才会使用路由
  • 过滤器(Filter): 通过过滤器,我们可以在执行路由前后加入自己的逻辑,Gateway存在全局过滤器和局部过滤器
  • 路由(Route): 路由是一个最基本的组件,它由ID、目标URI、断言集合和过滤器集合等组成,当断言判断为true时,才会匹配到路由,一个路由可以存在多个断言也可以存在多个过滤器

在Gateway的源码中已经提供了很多过滤器和断言供我们使用,例如之前代码中的path和stripPrefix都是Gateway提供的断言和过滤器.

Gateway的断言是通过它的路由断言工厂提供的,这些断言工厂都实现了接口RoutePredicateFactory<C>,源码如下所示

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;
/**
* 路由断言工厂接口,用于创建匹配网关中路由的断言。
* 它扩展了ShortcutConfigurable与Configurable接口,支持配置快捷方式及带有泛型C的可配置功能。
* @param <C> 配置类型,用于断言的特定配置。
*/
@FunctionalInterface
public interface RoutePredicateFactory<C> extends ShortcutConfigurable, Configurable<C> {
/**
* 断言配置中的模式键。
*/
String PATTERN_KEY = "pattern";
/**
* 应用给定的配置来创建一个断言。
* @param consumer 配置断言语句的消费者。
* @return 基于给定配置匹配路由的断言。
*/
default Predicate<ServerWebExchange> apply(Consumer<C> consumer) {
C config = this.newConfig();
consumer.accept(config);
this.beforeApply(config);
return this.apply(config);
}
/**
* 应用给定的配置来创建一个异步断言。
* @param consumer 配置异步断言语句的消费者。
* @return 基于给定配置异步匹配路由的AsyncPredicate。
*/
default AsyncPredicate<ServerWebExchange> applyAsync(Consumer<C> consumer) {
C config = this.newConfig();
consumer.accept(config);
this.beforeApply(config);
return this.applyAsync(config);
}
/**
* 获取配置类的类型。
* @return 配置类的类型。
* @throws UnsupportedOperationException 如果此方法未实现。
*/
default Class<C> getConfigClass() {
throw new UnsupportedOperationException("getConfigClass() 方法未实现");
}
/**
* 创建配置的新实例。
* @return 配置的新实例。
* @throws UnsupportedOperationException 如果此方法未实现。
*/
default C newConfig() {
throw new UnsupportedOperationException("newConfig() 方法未实现");
}
/**
* 在应用配置前调用的钩子方法。
* @param config 将要应用的配置。
*/
default void beforeApply(C config) {
}
/**
* 根据给定的配置创建一个断言。
* @param config 用于断言的配置。
* @return 基于给定配置匹配路由的断言。
*/
Predicate<ServerWebExchange> apply(C config);
/**
* 根据给定的配置创建一个异步断言。
* @param config 用于AsyncPredicate的配置。
* @return 基于给定配置异步匹配路由的AsyncPredicate。
*/
default AsyncPredicate<ServerWebExchange> applyAsync(C config) {
return ServerWebExchangeUtils.toAsyncPredicate(this.apply(config));
}
/**
* 返回断言的名称,该名称已规范化,适用于路由中使用。
* @return 断言的名称。
*/
default String name() {
return NameUtils.normalizeRoutePredicateName(this.getClass());
}
}

它有很多实现类,通过这些实现类实现了多种断言

这些类的命名非常关键,其规则都是XXXRoutePredicateFactory,这个规则很重要,例如之前的用到的path方法,就意味着它是使用PathRoutePredicateFactory生成的断言,在配置文件中的- Path=/customer-api/**也是使用PathRoutePredicateFactory生成的断言

如果想使用Query断言,可以这样改造代码,如下所示

package com.scd.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
/**
* 创建路由规则
* @param builder -- 路由构造器
* @return 路由规则
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 配置路由
// route方法两个参数:第一个是id;第二个断言
.route("customer", r -> r.path("/customer-api/**")
// 需要存在请求参数“id” ,才匹配路由
.and().query("id")
// 需要存在请求参数“id” ,且是数字才匹配路由
.and().query("id", "^[0-9]*$")
// 过滤器,删除一个层级再匹配地址
.filters(f->f.stripPrefix(1))
// 转发到具体的URI
.uri("http://localhost:3001"))
// 基于服务发现的路由
.route("goods", r->r.path("/goods-api/**")
// 过滤器,删除一个层级再匹配地址
.filters(f->f.stripPrefix(1)
// 添加响应头
.addResponseHeader("response-header", "response-value"))
// 约定以“lb://{service-id}”为格式
.uri("lb://goods"))
// 创建
.build();
}
}

通过query()添加了一个参数断言,判断存在参数id才会匹配路由,而第二个query()又判断了存在参数id且是一个数字才会匹配路由,在配置文件中也可以做同样的事情

# Spring应用(微服务)名称
spring:
application:
name: gateway
cloud:
gateway:
# 开始配置路径
routes:
# 路径匹配
- id: customer
# 转发URI
uri: http://localhost:3001
# 断言配置
predicates:
- Path=/customer-api/**
- Query=id
- Query=id, ^[0-9]*$
# 过滤器配置
filters:
- StripPrefix=1
# 路径匹配
- id: goods
# 转发URI
uri: lb://goods
# 断言配置
predicates:
- Path=/goods-api/**
# 过滤器配置
filters:
- StripPrefix=1
# 向端口为1001和1002的Eureka服务治理中心注册
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1001/eureka, http://localhost:1002/eureka
# 配置启动端口
server:
port: 6001
logging:
level:
root: info

过滤器工厂

过滤器允许在路由前后执行自己的逻辑,Gateway提供了许多过滤器工厂来产生过滤器,这些过滤器工厂都实现了接口GatewayFilterFactory<C>,源码如下

package org.springframework.cloud.gateway.filter.factory;
import java.util.function.Consumer;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.support.Configurable;
import org.springframework.cloud.gateway.support.HasRouteId;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.cloud.gateway.support.ShortcutConfigurable;
import org.springframework.http.server.reactive.ServerHttpRequest;
/**
* GatewayFilter工厂接口,用于创建GatewayFilter实例。
* 提供了配置GatewayFilter的灵活性,支持通过函数式接口进行配置。
*
* @param <C> 配置类型的参数化类型。
*/
@FunctionalInterface
public interface GatewayFilterFactory<C> extends ShortcutConfigurable, Configurable<C> {
/**
* 配置名称的键。
*/
String NAME_KEY = "name";
/**
* 配置值的键。
*/
String VALUE_KEY = "value";
/**
* 根据给定的routeId和配置消费者创建并应用GatewayFilter。
*
* @param routeId 路由ID,用于配置GatewayFilter。
* @param consumer 配置消费者,用于定制GatewayFilter的配置。
* @return 应用了配置的GatewayFilter实例。
*/
default GatewayFilter apply(String routeId, Consumer<C> consumer) {
C config = this.newConfig();
consumer.accept(config);
return this.apply(routeId, config);
}
/**
* 创建并应用GatewayFilter,使用配置消费者进行配置。
*
* @param consumer 配置消费者,用于定制GatewayFilter的配置。
* @return 应用了配置的GatewayFilter实例。
*/
default GatewayFilter apply(Consumer<C> consumer) {
C config = this.newConfig();
consumer.accept(config);
return this.apply(config);
}
/**
* 获取配置类的Class对象。
* 默认实现抛出UnsupportedOperationException异常,表明该方法未实现。
*
* @return 配置类的Class对象。
*/
default Class<C> getConfigClass() {
throw new UnsupportedOperationException("getConfigClass() not implemented");
}
/**
* 创建一个新的配置实例。
* 默认实现抛出UnsupportedOperationException异常,表明该方法未实现。
*
* @return 配置的实例。
*/
default C newConfig() {
throw new UnsupportedOperationException("newConfig() not implemented");
}
/**
* 根据给定的配置创建并应用GatewayFilter。
*
* @param config 配置实例,用于定制GatewayFilter。
* @return 应用了配置的GatewayFilter实例。
*/
GatewayFilter apply(C config);
/**
* 根据当前工厂类的名称生成并返回标准化的过滤器名称。
*
* @return 标准化的过滤器名称。
*/
default String name() {
return NameUtils.normalizeFilterFactoryName(this.getClass());
}
/**
* 修改ServerHttpRequest的构建器。
* 默认实现抛出UnsupportedOperationException异常,表明该方法未实现。
*
* @param request 原始请求。
* @return 修改后的请求构建器。
* @deprecated 该方法已过时。
*/
@Deprecated
default ServerHttpRequest.Builder mutate(ServerHttpRequest request) {
return request.mutate();
}
}

这些过滤器工厂类都继承了接口,同样的也需要特别注意这些工厂的命名规则XXXGatewayFilterFactory,以AddResponseHeaderGatewayFilterFactory为例,修改之前的代码如下

package com.scd.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
/**
* 创建路由规则
* @param builder -- 路由构造器
* @return 路由规则
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 配置路由
// route方法两个参数:第一个是id;第二个断言
.route("customer", r -> r.path("/customer-api/**")
// 需要存在请求参数“id” ,才匹配路由
.and().query("id")
// 需要存在请求参数“id” ,且是数字才匹配路由
.and().query("id", "^[0-9]*$")
// 过滤器,删除一个层级再匹配地址
.filters(f->f.stripPrefix(1))
// 转发到具体的URI
.uri("http://localhost:3001"))
// 基于服务发现的路由
.route("goods", r->r.path("/goods-api/**")
// 过滤器,删除一个层级再匹配地址
.filters(f->f.stripPrefix(1)
// 添加响应头
.addResponseHeader("response-header", "response-value"))
// 约定以“lb://{service-id}”为格式
.uri("lb://goods"))
// 创建
.build();
}
}

也可以通过配置文件实现,如下所示

# Spring应用(微服务)名称
spring:
application:
name: gateway
cloud:
gateway:
# 开始配置路径
routes:
# 路径匹配
- id: customer
# 转发URI
uri: http://localhost:3001
# 断言配置
predicates:
- Path=/customer-api/**
- Query=id
- Query=id, ^[0-9]*$
# 过滤器配置
filters:
- StripPrefix=1
# 路径匹配
- id: goods
# 转发URI
uri: lb://goods
# 断言配置
predicates:
- Path=/goods-api/**
# 过滤器配置
filters:
- AddResponseHeader=response-header, response-value
- StripPrefix=1
# 向端口为1001和1002的Eureka服务治理中心注册
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1001/eureka, http://localhost:1002/eureka
# 配置启动端口
server:
port: 6001
logging:
level:
root: info

主要用来给客户端响应的时候添加一个响应头

自定义过滤器

在Gateway中,过滤器分为全局过滤器和局部过滤器,全局过滤器对所有路由有效,局部过滤器对指定的路由有效,局部过滤器只需要实现GateFilter接口即可,而全局过滤器需要实现GlobalFilter接口, 并将其装配到Spring IoC容器中

改造一下之前的customRouteLocator方法,加入自定义局部过滤器,如下所示

package com.scd.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import reactor.core.publisher.Mono;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
/**
* 创建路由规则
* @param builder -- 路由构造器
* @return 路由规则
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 配置路由
// route方法两个参数:第一个是id;第二个断言
.route("customer", r -> r.path("/customer-api/**")
// 需要存在请求参数“id” ,才匹配路由
.and().query("id")
// 需要存在请求参数“id” ,且是数字才匹配路由
.and().query("id", "^[0-9]*$")
// 过滤器,删除一个层级再匹配地址
.filters(f->f.stripPrefix(1))
// 转发到具体的URI
.uri("http://localhost:3001"))
// 基于服务发现的路由
.route("goods", r->r.path("/goods-api/**")
// 过滤器,删除一个层级再匹配地址
.filters(f->f.stripPrefix(1)
.filter(myGatewayFilter()))
// 约定以“lb://{service-id}”为格式
.uri("lb://goods"))
// 创建
.build();
}
// 开发局部过滤器
private GatewayFilter myGatewayFilter() {
return (exchange, chain) -> {
System.out.println("我是局部过滤器逻辑");
// 获取请求对象
ServerHttpRequest request = exchange.getRequest();
// 增加请求头信息
request = request.mutate().header("request-header", "my-request-header").build();
/**
错误增加请求头的代码,
因为request.getHeaders()返回的是只读请求头,不可修改
*/
// request.getHeaders().add("header", "myheader");
// 获取请求参数
String id = request.getQueryParams().getFirst("id");
// 调用filter方法,让过滤器责任链向下继续执行
Mono<Void> mono = chain.filter(exchange);
// 获取应答对象
ServerHttpResponse response = exchange.getResponse();
// 响应类型为JSON
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
// 添加响应头
response.getHeaders().add("response-header", "my-response-header");
return mono;
};
}
}
  • .filter(myGatewayFilter())):filter方法调用了自定义的myGatewayFilter()局部过滤器方法返回的过滤器,通过这样给路由添加过滤器
  • 在myGatewayFilter()方法中,使用了正则表达式,创建局部过滤器,它需要实现的方法是Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);,其中参数exchange是一个请求交互对象,我们可以通过它来获取请求对象(ServerHttpRequest),正如代码中用它设置了请求头并获取了参数;当然也可以通过参数exchange获取应答对象(ServerHttpResponse),正如代码中用它设置了响应类型和响应头;而chain是一个过滤器责任链,通过它的filter方法继续执行下一层次的过滤器
  • //request.getHeaders().add("header", "myheader");注释掉的这个方法,在运行中会出现问题,request.getHeaders()返回的是一个只读的请求头对象,不可以写入,这个写法设置请求头会抛异常表示不支持该方法

在Gateway中还可以使用全局过滤器,全局过滤器对所有路由有效,使用它非常简单,在启动类中添加如下全局过滤器代码,如下所示

// 定义全局过滤器
@Bean
public GlobalFilter globalFilter() {
return (exchange, chain) -> {
System.out.println("我是全局过滤器");
Mono<Void> mono = chain.filter(exchange);
return mono;
};
}

方法标注了@Bean,代表会将其返回的全局过滤器(GlobalFilter接口对象)装配到SpringIoC容器中,此时Gateway会自动识别,将其装配到过滤器的责任链中;在globalFilter方法中,通过Lambda表达式创建了GlobalFilter接口对象,其参数和局部过滤器GatewayFilter接口一致

最终启动类如下代码所示

package com.scd.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import reactor.core.publisher.Mono;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
/**
* 创建路由规则
* @param builder -- 路由构造器
* @return 路由规则
*/
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 配置路由
// route方法两个参数:第一个是id;第二个断言
.route("customer", r -> r.path("/customer-api/**")
// 过滤器,删除一个层级再匹配地址
.filters(f->f.stripPrefix(1))
// 转发到具体的URI
.uri("http://localhost:3001"))
// 基于服务发现的路由
.route("goods", r->r.path("/goods-api/**")
// 过滤器,删除一个层级再匹配地址
.filters(f->f.stripPrefix(1)
.filter(myGatewayFilter()))
// 约定以“lb://{service-id}”为格式
.uri("lb://goods"))
// 创建
.build();
}
// 开发局部过滤器
private GatewayFilter myGatewayFilter() {
return (exchange, chain) -> {
System.out.println("我是局部过滤器逻辑");
// 获取请求对象
ServerHttpRequest request = exchange.getRequest();
// 增加请求头信息
request = request.mutate().header("request-header", "my-request-header").build();
/**
错误增加请求头的代码,
因为request.getHeaders()返回的是只读请求头,不可修改
*/
// request.getHeaders().add("header", "myheader");
// 获取请求参数
String id = request.getQueryParams().getFirst("id");
// 调用filter方法,让过滤器责任链向下继续执行
Mono<Void> mono = chain.filter(exchange);
// 获取应答对象
ServerHttpResponse response = exchange.getResponse();
//响应类型为JSON
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
// 添加响应头
response.getHeaders().add("response-header", "my-response-header");
return mono;
};
}
// 定义全局过滤器
@Bean
public GlobalFilter globalFilter() {
return (exchange, chain) -> {
System.out.println("我是全局过滤器");
Mono<Void> mono = chain.filter(exchange);
return mono;
};
}
}

用6001端口启动Gateway模块,然后访问

http://localhost:6001/customer-api/customer/name/1

http://localhost:6001/goods-api/goods/customer/name/1

会得到如下页面

并且在Gateway的控制台能够看到如下输出,说明网关配置生效

文章目录
  1. 1. 网关Gateway
  2. 2. 通过代码配置路由
  3. 3. 通过配置文件配置路由
  4. 4. 路由断言工厂
  5. 5. 过滤器工厂
  6. 6. 自定义过滤器