欢迎来到我的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模块,否则会发生冲突。

com.yx

cloud

1.0-SNAPSHOT

org.springframework.boot

spring-boot-starter-webflux

org.springframework.cloud

spring-cloud-starter-gateway

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 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 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 predicates = new ArrayList<>();

//路由过滤器集合

private List 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 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 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 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的依赖,因为配置类中需要

com.alibaba

fastjson

1.2.35

         导入依赖之后记得更新刷新配置类防止报错。

 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文件中生产者的访问路径进行该功重新访问 

        我们更新之后先访问没更改前的请求路径,在访问更改后的请求路径。 

        这就是我们的动态路由的实现方式 

 本期的博客分享到此结束

各位老铁慢慢消化

下期博客博主会带来新货

三连加关注,阅读不迷路 !

参考阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: