Spring Cloud gateway 网关四 动态路由

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: Spring Cloud gateway 网关四 动态路由

微服务当前这么火爆的程度,如果不能学会一种微服务框架技术。怎么能升职加薪,增加简历的筹码?spring cloud 和 Dubbo 需要单独学习。说没有时间?没有精力?要学俩个框架?而Spring Cloud alibaba只需要你学会一个就会拥有俩种微服务治理框架技术。何乐而不为呢?加油吧!骚猿年

在之前的 zuul 我们讲了。怎么去动态的获取路由。作为Spring Cloud 亲儿子的存在 gateway 不可能不支持动态路由。今天我们初探一下gateway 的动态路由。

需求前置。在了解动态路由。可能会能想到,我们的数据要存储在数据库中或者redis 当中。还要有存储的实体类。

思考:

配置中心刷新routes配置信息。路由信息刷新改变。利用事件发布,利用配置中心完成动态刷新路由。本次改造我们使用自定义存储方式达到手动触发动态路由
 
InMemoryRouteDefinitionRepository 默认使用。这个类就是把握们当前的所有的路由routes 存储在内存当中。当服务重启或者刷新,内存就不复存在。ps: 因为本项目是用nacos 注册中心也是配置中心。可以存储在nacos 配置中心里面。
RouteDefinitionRepository 接口是InMemoryRouteDefinitionRepository 是它的接口类 继承了 RouteDefinitionLocator 、RouteDefinitionWriter俩个接口。

RouteDefinitionLocator 接口

RouteDefinitionWriter 用来实现路由的添加与删除。

思考:
基于这个原则我们在动态添加或者删除路由的时候,就可以根据这个接口实现去满足我们动态的控制路由规则。

RouteDefinitionRepository 这个接口成为了关键点我们来重新动态路由其实也是基于这个接口来实现

实体类

package com.xian.cloud.model;
 
import lombok.Data;
 
/**
 * @Author: xlr
 * @Date: Created in 5:10 PM 2019/9/29
 */
@Data
public class GatewayRoutesEntity {
 
    private Long id;
 
    private String serviceId;
 
    private String uri;
 
    private String predicates;
 
    private String filters;
 
}

GatewayRoutesService 接口

package com.xian.cloud.service;
 
import com.xian.cloud.model.GatewayRoutesEntity;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
 
import java.util.List;
 
/**
 * <Description>
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/11/08 16:08
 */
public interface GatewayRoutesService {
 
    List<GatewayRoutesEntity> findAll() throws Exception;
 
    String loadRouteDefinition() throws Exception;
 
    GatewayRoutesEntity save(GatewayRoutesEntity gatewayDefine) throws Exception;
 
    void deleteById(String id) throws Exception;
 
    boolean existsById(String id)throws Exception;
 
    List<PredicateDefinition> getPredicateDefinition(String predicates) ;
 
    List<FilterDefinition> getFilterDefinition(String filters) ;
 
}

GatewayRoutesService 实现类

package com.xian.cloud.service.impl;
 
import com.alibaba.fastjson.JSON;
import com.xian.cloud.model.GatewayRoutesEntity;
import com.xian.cloud.service.GatewayRoutesService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
 
import java.net.URI;
import java.util.List;
 
/**
 * <Description>
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/11/08 16:09
 */
@Service
@Slf4j
public class GatewayRoutesServiceImpl implements GatewayRoutesService {
 
    public static final String GATEWAY_DEFINE_LIST_KEY = "gateway_routes_list_key";
 
    @Autowired
    private RedisTemplate redisTemplate;
 
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
 
    private ApplicationEventPublisher publisher;
 
    @Override
    public List <GatewayRoutesEntity> findAll() throws Exception {
 
        Long size = redisTemplate.opsForList().size( GATEWAY_DEFINE_LIST_KEY );
        List<GatewayRoutesEntity> list = redisTemplate.opsForList().range( GATEWAY_DEFINE_LIST_KEY, 0, size );
        return list;
    }
 
    @Override
    public String loadRouteDefinition() {
        try {
            List <GatewayRoutesEntity> gatewayDefineServiceAll = findAll();
            if (gatewayDefineServiceAll == null) {
                return "none route defined";
            }
            for (GatewayRoutesEntity gatewayDefine : gatewayDefineServiceAll) {
                RouteDefinition definition = new RouteDefinition();
                definition.setId( gatewayDefine.getServiceId() );
                definition.setUri( new URI( gatewayDefine.getUri() ) );
                List <PredicateDefinition> predicateDefinitions = getPredicateDefinition(gatewayDefine.getPredicates());
                if (predicateDefinitions != null) {
                    definition.setPredicates( predicateDefinitions );
                }
                List <FilterDefinition> filterDefinitions = getFilterDefinition(gatewayDefine.getFilters());
                if (filterDefinitions != null) {
                    definition.setFilters( filterDefinitions );
                }
                routeDefinitionWriter.save( Mono.just( definition ) ).subscribe();
                publisher.publishEvent( new RefreshRoutesEvent( this ) );
            }
            return "success";
        } catch (Exception e) {
            e.printStackTrace();
            return "failure";
        }
    }
 
    /**
     * 获取所有的 自定义路由规则
     * @param gatewayDefine
     * @return
     * @throws Exception
     */
    @Override
    public GatewayRoutesEntity save(GatewayRoutesEntity gatewayDefine) throws Exception {
        log.info( "save RouteDefinition : {}",gatewayDefine );
        redisTemplate.opsForList().rightPush(  GATEWAY_DEFINE_LIST_KEY, gatewayDefine );
        return gatewayDefine;
    }
 
    @Override
    public void deleteById(String id) throws Exception {
        List <GatewayRoutesEntity> all = findAll();
        for (GatewayRoutesEntity gatewayDefine : all) {
            if(gatewayDefine.getServiceId().equals( id )){
                redisTemplate.opsForList().remove( GATEWAY_DEFINE_LIST_KEY,0, gatewayDefine);
            }
        }
    }
 
    @Override
    public boolean existsById(String id) throws Exception {
        List <GatewayRoutesEntity> all = findAll();
        for (GatewayRoutesEntity gatewayDefine : all) {
            if(gatewayDefine.getServiceId().equals( id )){
                return true;
            }
        }
        return false;
    }
 
    @Override
    public List<PredicateDefinition> getPredicateDefinition(String predicates) {
        if ( StringUtils.isNotBlank( predicates )) {
            List<PredicateDefinition> predicateDefinitionList = JSON.parseArray(predicates, PredicateDefinition.class);
            return predicateDefinitionList;
        } else {
            return null;
        }
    }
    @Override
    public List<FilterDefinition> getFilterDefinition(String filters) {
        if (StringUtils.isNotBlank( filters )) {
            List<FilterDefinition> filterDefinitionList = JSON.parseArray(filters, FilterDefinition.class);
            return filterDefinitionList;
        } else {
            return null;
        }
    }
}

然后我们在进行 RouteDefinitionLocator 、RouteDefinitionWriter 俩个接口的声明 在配置文件中

package com.xian.cloud.config;
 
import com.xian.cloud.repository.GatewayRoutesRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
 
/**
 * <Description> 动态更新路由
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/11/08 17:12
 */
@Configuration
@Slf4j
public class GatewayRoutesDefinitionConfig {
 
 
    @Bean
    RouteDefinitionLocator routeDefinitionLocator(){
        return new GatewayRoutesRepository();
    }
        
    @Bean
    @Primary
    RouteDefinitionWriter routeDefinitionWriter(){
        return new GatewayRoutesRepository();
    }
}

RefreshRoutesEvent 事件发布刷新routes事件,通知网关。 这个事件是gateway 的事件。

package com.xian.cloud.event;
 
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
 
/**
 * <Description>
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/11/08 17:20
 */
@Component
@Slf4j
public class RefreshRouteService implements ApplicationEventPublisherAware {
 
    private ApplicationEventPublisher publisher;
 
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }
 
    /**
     * 刷新路由表
     */
    public void refreshRoutes() {
        publisher.publishEvent(new RefreshRoutesEvent(this));
    }
}

然后我们还差一个手动触发的接口,创建GatewayRoutesController

package com.xian.cloud.controller;
 
import com.xian.cloud.event.RefreshRouteService;
import com.xian.cloud.model.RestResult;
import com.xian.cloud.model.RestResultBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
/**
 * <Description>
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/11/08 17:18
 */
@RestController
@RequestMapping("/gateway")
 
public class GatewayRoutesController {
 
    @Autowired
    private RefreshRouteService refreshRouteService;
 
    @GetMapping("/refreshRoutes")
    public RestResult refreshRoutes(){
        refreshRouteService.refreshRoutes();
        return RestResultBuilder.builder().success().build();
    }
}

到此为止,就完成了所有的代码。

启动服务

其实还有一种很简单直接的写法。参考重新定义 Spring Cloud 实战中的写法。

创建事件增加类DynamicRouteServiceImpl

package com.xian.cloud.event;
 
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
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.stereotype.Service;
import reactor.core.publisher.Mono;
 
/**
 * <Description>
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/11/09 10:40
 */
@Slf4j
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
 
    @Qualifier("routeDefinitionRepositor")
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
 
    private ApplicationEventPublisher publisher;
 
    /**
     * 添加路由实体类
     * @param definition
     * @return
     */
    public boolean add(RouteDefinition definition){
        routeDefinitionWriter.save((Mono<RouteDefinition>) Mono.just(definition).subscribe());
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return true;
    }
 
    /**
     *
     * @param definition 路由实体类
     * @return
     */
    public boolean update(RouteDefinition definition){
        try {
            routeDefinitionWriter.delete(Mono.just(definition.getId()));
        }catch (Exception e){
            log.error("update 失败。没有找到对应的路由ID :{}",definition.getId());
        }
 
        routeDefinitionWriter.save((Mono<RouteDefinition>) (Mono.just(definition)).subscribe());
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return true;
    }
 
    /**
     * serviceId
     * @param id
     * @return
     */
    public boolean del(String id){
        routeDefinitionWriter.delete(Mono.just(id));
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return true;
    }
 
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
}

修改controller 添加三个方法

package com.xian.cloud.controller;
 
import com.xian.cloud.event.DynamicRouteServiceImpl;
import com.xian.cloud.event.RefreshRouteService;
import com.xian.cloud.model.RestResult;
import com.xian.cloud.model.RestResultBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.web.bind.annotation.*;
 
/**
 * <Description>
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/11/08 17:18
 */
@RestController
@RequestMapping("/gateway")
public class GatewayRoutesController {
 
    @Autowired
    private RefreshRouteService refreshRouteService;
 
    @Autowired
    private DynamicRouteServiceImpl dynamicRouteService;
 
    @GetMapping("/refreshRoutes")
    public RestResult refreshRoutes(){
        refreshRouteService.refreshRoutes();
        return RestResultBuilder.builder().success().build();
    }
 
    /**
     *
     * @param definition
     * @return
     */
    @RequestMapping(value = "routes/add",method = RequestMethod.POST)
    public RestResult add(@RequestBody RouteDefinition definition){
        boolean flag = dynamicRouteService.add(definition);
        if(flag){
            return RestResultBuilder.builder().success().build();
        }
        return RestResultBuilder.builder().failure().build();
    }
 
    /**
     *
     * @param definition
     * @return
     */
    @RequestMapping(value = "routes/update",method = RequestMethod.POST)
    public RestResult update(@RequestBody RouteDefinition definition){
        boolean flag = dynamicRouteService.add(definition);
        if(flag){
            return RestResultBuilder.builder().success().build();
        }
        return RestResultBuilder.builder().failure().build();
    }
 
    /**
     *
     * @param serviceId
     * @return
     */
    @RequestMapping(value = "routes/del",method = RequestMethod.POST)
    public RestResult update(@RequestParam("serviceId") String serviceId){
        boolean flag = dynamicRouteService.del(serviceId);
        if(flag){
            return RestResultBuilder.builder().success().build();
        }
        return RestResultBuilder.builder().failure().build();
    }
}

增删改。三个接口对外暴露。

以上就是gateway 的动态刷新路由。

思考

动态刷新能满足我们在服务运维上的管理方便。但是因为这个整体使用的nacos 配置中心做的。这样做的成本考虑。动态刷新是不是真的有必要这么做。完全可以在配置中心配置然后刷新。就能触发路由的整体刷新。如果需要自己搭建运维管理中心。有自己的管理体系可以实现动态路由。

摘自参考 spring cloud 官方文档

示例代码地址

服务器nacos 地址 http://47.99.209.72:8848/nacos

往期地址 spring cloud 文章地址

spring cloud alibaba 简介

Spring Cloud Alibaba (nacos 注册中心搭建)

Spring Cloud Alibaba 使用nacos 注册中心

Spring Cloud Alibaba nacos 配置中心使用

spring cloud 网关服务

Spring Cloud zuul网关服务 一

Spring Cloud 网关服务 zuul 二

Spring Cloud 网关服务 zuul 三 动态路由

Spring Cloud alibaba网关 sentinel zuul 四 限流熔断

Spring Cloud gateway 网关服务 一

Spring Cloud gateway 网关服务二 断言、过滤器

Spring Cloud gateway 三 自定义过滤器GatewayFilter

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。转载请附带公众号二维码

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
183 1
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
117 62
|
19天前
|
JavaScript Java Kotlin
深入 Spring Cloud Gateway 过滤器
Spring Cloud Gateway 是新一代微服务网关框架,支持多种过滤器实现。本文详解了 `GlobalFilter`、`GatewayFilter` 和 `AbstractGatewayFilterFactory` 三种过滤器的实现方式及其应用场景,帮助开发者高效利用这些工具进行网关开发。
112 1
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
84 2
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用
【10月更文挑战第8天】本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,通过 Spring Initializr 创建并配置 Spring Boot 项目,实现后端 API 和安全配置。接着,使用 Ant Design Pro Vue 脚手架创建前端项目,配置动态路由和菜单,并创建相应的页面组件。最后,通过具体实践心得,分享了版本兼容性、安全性、性能调优等注意事项,帮助读者快速搭建高效且易维护的应用框架。
62 3
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用
【10月更文挑战第7天】本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,通过 Spring Initializr 创建 Spring Boot 项目并配置 Spring Security。接着,实现后端 API 以提供菜单数据。在前端部分,使用 Ant Design Pro Vue 脚手架创建项目,并配置动态路由和菜单。最后,启动前后端服务,实现高效、美观且功能强大的应用框架。
64 2
|
2月前
|
XML Java 数据格式
如何使用 Spring Cloud 实现网关
如何使用 Spring Cloud 实现网关
49 3
|
3月前
|
Java 开发者 Spring
Spring Cloud Gateway 中,过滤器的分类有哪些?
Spring Cloud Gateway 中,过滤器的分类有哪些?
82 3
|
3月前
|
负载均衡 Java 网络架构
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
169 5
|
2月前
|
负载均衡 Java API
【Spring Cloud生态】Spring Cloud Gateway基本配置
【Spring Cloud生态】Spring Cloud Gateway基本配置
59 0