🎉🎉欢迎来到我的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: gatewaycloud:nacos:discovery:server-addr: localhost:8848gateway:discovery:locator:enabled: truelower-case-service-id: true
4. 启动类标记使用nacos
我们记得在网关启动类上的标记启用nacos的注解,启用其nacos
5. 使用网关的方式
5.1 方式一:
1. 对其yml文件配置
在网关的yml文件中配置下述内容
spring:application:
# 名称name: gatewaycloud:nacos:discovery:
# 地址server-addr: localhost:8848gateway:discovery:locator:enabled: truelower-case-service-id: true
2. 测试演示
我们在我们要访问的请求方法中编写一个输入语句便于测试
我们在网页进行测试
有上述的动图所示,我们先是访问使用生成者的接口访问其请求方法;然后我们在使用网关的接口访问其生产者的请求接口,该生产者控制台输出对应的输出语句。
5.2 方式二:
1. 对其yml文件进行配置
我们将之前方式一的yml文件注释掉,配置方式二所需的yml文件
方式二:
# 设置路由 规则routes:
# -路由标识- id: user-provider-api#目标服务地址(uri:地址,请求转发后的地址),会自动从注册中心获得服务的IP,不需要手动写死uri: lb://providerpredicates:- # 路径匹配,- 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 {//过滤器对应的Nameprivate 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 {//断言对应的Nameprivate 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 {//路由idprivate String id;//路由断言集合private List<PredicateEntity> predicates = new ArrayList<>();//路由过滤器集合private List<FilterEntity> filters = new ArrayList<>();//路由转发的目标uriprivate 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 {@Autowiredprivate RouteDefinitionWriter routeDefinitionWriter;@Autowiredprivate GatewayNacosProperties gatewayProperties;@Autowiredprivate ObjectMapper mapper;private ApplicationEventPublisher applicationEventPublisher;@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {this.applicationEventPublisher = applicationEventPublisher;}/*** 这个方法主要负责监听Nacos的配置变化,这里先使用参数构建一个ConfigService,* 再使用ConfigService开启一个监听,* 并且在监听的方法中刷新路由信息。*/@Beanpublic 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() {@Overridepublic Executor getExecutor() {return null;}/*** 用于接收远端nacos中数据修改后的回调方法*/@Overridepublic 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();// IDdefinition.setId(routeEntity.getId());// PredicatesList<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);// FiltersList<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);// URIURI 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文件中生产者的访问路径进行该功重新访问
我们更新之后先访问没更改前的请求路径,在访问更改后的请求路径。
这就是我们的动态路由的实现方式
🎉🎉本期的博客分享到此结束🎉🎉
📚📚各位老铁慢慢消化📚📚
🎯🎯下期博客博主会带来新货🎯🎯
🎁三连加关注,阅读不迷路 !🎁