🎉🎉欢迎来到我的CSDN主页!🎉🎉
🏅我是君易--鑨,一个在CSDN分享笔记的博主。📚📚
🌟推荐给大家我的博客专栏《SpringCloud开发之网关应用》。🎯🎯
🎁如果感觉还不错的话请给我关注加三连吧!🎁🎁
前言
在上一期的博客分享中我们一起了解到了SpringCloud的配置中心的相关知识的学习以及应用的方式,本期的博客分享给大家带来的是SpringCloud的网关应用。
一、什么是网关
1. 基本概述
Spring Cloud Gateway是Spring官方基于Spring5.0、SpringBoot2.0和Project Reactor等技术开发的网关旨在为微服务框架提供一种简单而有效的统一的API路由管理方式,统一访问接口。
Spring Cloud Gateway作为Spring Cloud生态体系中的网关,目标是替代Netflix的Zuul,其不仅提供统 一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全、监控/埋点和限流等等。 它是基于Netty的响应式开发模式。
2. 图解
3. 组成结构
1️⃣ 路由(route ):路由是网关最基础的部分,路由信息由一个 ID ,一个目的 URL 、一组断言工厂和一组Filter 组成。如果断言为真,则说明请求 URL 和配置的路由匹配。2️⃣ 断言(Predicate ): Java8 中的断言函数, Spring Cloud Gateway 中的断言函数输入类型是Spring5.0框架中的 ServerWebExchange 。 Spring Cloud Gateway 中的断言函数允许开发者去定义匹配来自http Request 中的任何信息,比如请求头和参数等。3️⃣ 过滤器(Filter ):一个标准的 Spring WebFilter , Spring Cloud Gateway 中的 Filter 分为两种类型:Gateway Filter和 Global Filter 。过滤器 Filter 可以对请求和响应进行处理。
4. 应用场景
二、网关的集成使用
1. 新建一个模块作为网关
我们在主项目下的创建一个SpringCloud的项目作为网关模块。
创建完成之后我们将网关模块的pom文件中自带的pom文件依赖给去除掉,包括跳过编译也去除掉。
2. pom文件设置
我们首先在创建好的pom文件中先集成我们主项目的pom文件,导入网关运行服务器的依赖和网关的依赖,但是注意主项目中有没有定义web模块,否则会发生冲突。
<!-- 集成主项目-->
<parent>
<groupId>com.yx</groupId>
<artifactId>cloud</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<!-- 导入网关服务器的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- 导入网关的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
3. 新建配置文件进行配置
我们在网关的项目中在main的目录下新建一个resource文件夹,在其文件夹下新建一个application.yml文件进行相应的配置
server:
port: 8082
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
4. 启动类标记使用nacos
我们记得在网关启动类上的标记启用nacos的注解,启用其nacos
5. 使用网关的方式
5.1 方式一:
1. 对其yml文件配置
在网关的yml文件中配置下述内容
spring:
application:
# 名称
name: gateway
cloud:
nacos:
discovery:
# 地址
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
2. 测试演示
我们在我们要访问的请求方法中编写一个输入语句便于测试
我们在网页进行测试
有上述的动图所示,我们先是访问使用生成者的接口访问其请求方法;然后我们在使用网关的接口访问其生产者的请求接口,该生产者控制台输出对应的输出语句。
5.2 方式二:
1. 对其yml文件进行配置
我们将之前方式一的yml文件注释掉,配置方式二所需的yml文件
方式二:
# 设置路由 规则
routes:
# -路由标识
- id: user-provider-api
#目标服务地址(uri:地址,请求转发后的地址),会自动从注册中心获得服务的IP,不需要手动写死
uri: lb://provider
predicates:
- # 路径匹配,
- Path=/prov/**
filters:
#路径前缀删除示例:请求/name/bar/foo,StripPrefix=2,去除掉前面两个前缀之后,最后转
# 发到目标服务的路径为/foo
#前缀过滤,请求地址:http://localhost:8084/usr/hello
#此处配置去掉1个路径前缀,再配置上面的 Path=/usr/**,就将**转发到指定的微服务
#因为这个api相当于是服务名,只是为了方便以后nginx的代码加上去的,对于服务提供者
# service-client来说,不需要这段地址,所以需要去掉
- StripPrefix=1
2. 重启项目测试
我们先继续访问之前的方式一的请求路径,结果页面上会显示报错404找不到该接口路径,当我们换成方式二的请求路径进行访问就可以访问成功。
这就是方式二的测试结果显示
5.3 方式三:
1. 编写配置类
我们先将其方式二的yml文件内注释掉,进行编写方式三的yml文件内容。
这是属于我们自定义的配置,所以还需要我们自定义的配置类,写在我们的网关服务中
FilterEntity.java
package com.yx.gateway.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author hgh
*/
@SuppressWarnings("all")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class FilterEntity {
//过滤器对应的Name
private String name;
//路由规则
private Map<String, String> args = new LinkedHashMap<>();
}
GatewayNacosProperties.java
package com.yx.gateway.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@SuppressWarnings("all")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@ConfigurationProperties(prefix = "gateway.nacos")
@Component
public class GatewayNacosProperties {
private String serverAddr;
private String dataId;
private String namespace;
private String group;
}
PredicateEntity.java
package com.yx.gateway.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author hgh
*/
@SuppressWarnings("all")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class PredicateEntity {
//断言对应的Name
private String name;
//断言规则
private Map<String, String> args = new LinkedHashMap<>();
}
RouteEntity.java
package com.yx.gateway.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.util.ArrayList;
import java.util.List;
/**
* @author hgh
*/
@SuppressWarnings("all")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class RouteEntity {
//路由id
private String id;
//路由断言集合
private List<PredicateEntity> predicates = new ArrayList<>();
//路由过滤器集合
private List<FilterEntity> filters = new ArrayList<>();
//路由转发的目标uri
private String uri;
//路由执行的顺序
private int order = 0;
}
DynamicRoutingConfig.java
package com.yx.gateway;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yx.gateway.pojo.FilterEntity;
import com.yx.gateway.pojo.GatewayNacosProperties;
import com.yx.gateway.pojo.PredicateEntity;
import com.yx.gateway.pojo.RouteEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
/**
* 此类实现了Spring Cloud Gateway + nacos 的动态路由,
* 它实现一个Spring提供的事件推送接口ApplicationEventPublisherAware
*/
@SuppressWarnings("all")
@Slf4j
@Component
public class DynamicRoutingConfig implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private GatewayNacosProperties gatewayProperties;
@Autowired
private ObjectMapper mapper;
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/**
* 这个方法主要负责监听Nacos的配置变化,这里先使用参数构建一个ConfigService,
* 再使用ConfigService开启一个监听,
* 并且在监听的方法中刷新路由信息。
*/
@Bean
public void refreshRouting() throws NacosException {
//创建Properties配置类
Properties properties = new Properties();
System.out.println(gatewayProperties);
//设置nacos的服务器地址,从配置类GatewayProperties中获取
properties.put(PropertyKeyConst.SERVER_ADDR, gatewayProperties.getServerAddr());
//设置nacos的命名空间,表示从具体的命名空间中获取配置信息,不填代表默认从public获得
if (gatewayProperties.getNamespace() != null) {
properties.put(PropertyKeyConst.NAMESPACE, gatewayProperties.getNamespace());
}
//根据Properties配置创建ConfigService类
ConfigService configService = NacosFactory.createConfigService(properties);
//获得nacos中已有的路由配置
String json = configService.getConfig(gatewayProperties.getDataId(), gatewayProperties.getGroup(), 5000);
this.parseJson(json);
//添加监听器,监听nacos中的数据修改事件
configService.addListener(gatewayProperties.getDataId(), gatewayProperties.getGroup(), new Listener() {
@Override
public Executor getExecutor() {
return null;
}
/**
* 用于接收远端nacos中数据修改后的回调方法
*/
@Override
public void receiveConfigInfo(String configInfo) {
log.warn(configInfo);
//获取nacos中修改的数据并进行转换
parseJson(configInfo);
}
});
}
/**
* 解析从nacos读取的路由配置信息(json格式)
*/
public void parseJson(String json) {
log.warn("从Nacos返回的路由配置(JSON格式):" + json);
boolean refreshGatewayRoute = JSONObject.parseObject(json).getBoolean("refreshGatewayRoute");
if (refreshGatewayRoute) {
List<RouteEntity> list = JSON.parseArray(JSONObject.parseObject(json).getString("routeList")).toJavaList(RouteEntity.class);
for (RouteEntity route : list) {
update(assembleRouteDefinition(route));
}
} else {
log.warn("路由未发生变更");
}
}
/**
* 路由更新
*/
public void update(RouteDefinition routeDefinition) {
try {
this.routeDefinitionWriter.delete(Mono.just(routeDefinition.getId()));
log.warn("路由删除成功:" + routeDefinition.getId());
} catch (Exception e) {
log.error(e.getMessage(), e);
}
try {
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
log.warn("路由更新成功:" + routeDefinition.getId());
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
/**
* 路由定义
*/
public RouteDefinition assembleRouteDefinition(RouteEntity routeEntity) {
RouteDefinition definition = new RouteDefinition();
// ID
definition.setId(routeEntity.getId());
// Predicates
List<PredicateDefinition> pdList = new ArrayList<>();
for (PredicateEntity predicateEntity : routeEntity.getPredicates()) {
PredicateDefinition predicateDefinition = new PredicateDefinition();
predicateDefinition.setArgs(predicateEntity.getArgs());
predicateDefinition.setName(predicateEntity.getName());
pdList.add(predicateDefinition);
}
definition.setPredicates(pdList);
// Filters
List<FilterDefinition> fdList = new ArrayList<>();
for (FilterEntity filterEntity : routeEntity.getFilters()) {
FilterDefinition filterDefinition = new FilterDefinition();
filterDefinition.setArgs(filterEntity.getArgs());
filterDefinition.setName(filterEntity.getName());
fdList.add(filterDefinition);
}
definition.setFilters(fdList);
// URI
URI uri = UriComponentsBuilder.fromUriString(routeEntity.getUri()).build().toUri();
definition.setUri(uri);
return definition;
}
}
2. 导入所需依赖
导入阿里的阿里的fastjson的依赖,因为配置类中需要
<!-- 导入阿里的fastjson的依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.35</version>
</dependency>
导入依赖之后记得更新刷新配置类防止报错。
3. 在nacos中创建一个json文件
我们进入nacos的官网中,在配置中心中创建一个json的配置文件,配置文件的名称要与yml文件中的id一致
json内容
{
"refreshGatewayRoute": true,
"routeList": [
{
"id": "consumer-api",
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/cum/**"
}
}
],
"filters": [
{
"name": "StripPrefix",
"args": {
"_genkey_0": "1"
}
}
],
"uri": "lb://consumer",
"order": 0
},
{
"id": "provider-api",
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/pvr/**"
}
}
],
"filters": [
{
"name": "StripPrefix",
"args": {
"_genkey_0": "1"
}
}
],
"uri": "lb://provider",
"order": 0
}
]
}
4. 测试效果
我们重新启动项目,在网页中进行测试效果
首先我们访问指定的请求路径测试,测试结果是能够成功访问
当我们前去把在nacos官网将该json文件中生产者的访问路径进行该功重新访问
我们更新之后先访问没更改前的请求路径,在访问更改后的请求路径。
这就是我们的动态路由的实现方式
🎉🎉本期的博客分享到此结束🎉🎉
📚📚各位老铁慢慢消化📚📚
🎯🎯下期博客博主会带来新货🎯🎯
🎁三连加关注,阅读不迷路 !🎁