🎉🎉欢迎来到我的CSDN主页!🎉🎉
🏅我是君易--鑨,一个在CSDN分享笔记的博主。📚📚
🌟推荐给大家我的博客专栏《SpringCloud开发之网关应用》。🎯🎯
🎁如果感觉还不错的话请给我关注加三连吧!🎁🎁
在上一期的博客分享中我们一起了解到了SpringCloud的配置中心的相关知识的学习以及应用的方式,本期的博客分享给大家带来的是SpringCloud的网关应用。
Spring Cloud Gateway是Spring官方基于Spring5.0、SpringBoot2.0和Project Reactor等技术开发的网关旨在为微服务框架提供一种简单而有效的统一的API路由管理方式,统一访问接口。
Spring Cloud Gateway作为Spring Cloud生态体系中的网关,目标是替代Netflix的Zuul,其不仅提供统 一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全、监控/埋点和限流等等。 它是基于Netty的响应式开发模式。
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 可以对请求和响应进行处理。
我们在主项目下的创建一个SpringCloud的项目作为网关模块。
创建完成之后我们将网关模块的pom文件中自带的pom文件依赖给去除掉,包括跳过编译也去除掉。
我们首先在创建好的pom文件中先集成我们主项目的pom文件,导入网关运行服务器的依赖和网关的依赖,但是注意主项目中有没有定义web模块,否则会发生冲突。
com.yx cloud1.0-SNAPSHOT org.springframework.boot spring-boot-starter-webfluxorg.springframework.cloud spring-cloud-starter-gateway
我们在网关的项目中在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
我们记得在网关启动类上的标记启用nacos的注解,启用其nacos
在网关的yml文件中配置下述内容
spring: application: # 名称 name: gateway cloud: nacos: discovery: # 地址 server-addr: localhost:8848 gateway: discovery: locator: enabled: true lower-case-service-id: true
我们在我们要访问的请求方法中编写一个输入语句便于测试
我们在网页进行测试
有上述的动图所示,我们先是访问使用生成者的接口访问其请求方法;然后我们在使用网关的接口访问其生产者的请求接口,该生产者控制台输出对应的输出语句。
我们将之前方式一的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
我们先继续访问之前的方式一的请求路径,结果页面上会显示报错404找不到该接口路径,当我们换成方式二的请求路径进行访问就可以访问成功。
这就是方式二的测试结果显示
我们先将其方式二的yml文件内注释掉,进行编写方式三的yml文件内容。
这是属于我们自定义的配置,所以还需要我们自定义的配置类,写在我们的网关服务中
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 Mapargs = new LinkedHashMap<>(); }
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; }
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 Mapargs = new LinkedHashMap<>(); }
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 Listpredicates = new ArrayList<>(); //路由过滤器集合 private List filters = new ArrayList<>(); //路由转发的目标uri private String uri; //路由执行的顺序 private int order = 0; }
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) { Listlist = 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; } }
导入阿里的阿里的fastjson的依赖,因为配置类中需要
com.alibaba fastjson1.2.35
导入依赖之后记得更新刷新配置类防止报错。
我们进入nacos的官网中,在配置中心中创建一个json的配置文件,配置文件的名称要与yml文件中的id一致
{ "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 } ] }
我们重新启动项目,在网页中进行测试效果
首先我们访问指定的请求路径测试,测试结果是能够成功访问
当我们前去把在nacos官网将该json文件中生产者的访问路径进行该功重新访问
我们更新之后先访问没更改前的请求路径,在访问更改后的请求路径。
这就是我们的动态路由的实现方式
🎉🎉本期的博客分享到此结束🎉🎉
📚📚各位老铁慢慢消化📚📚
🎯🎯下期博客博主会带来新货🎯🎯
🎁三连加关注,阅读不迷路 !🎁