SpringCloud-gateway编码实现路由策略的自动刷新,动态路由

文章目录

一、概述

1、背景

gateway可以配置路由断言过滤器,但是通常一个微服务体系下,一个gateway网关对应多个微服务,如果上线一个新的微服务或者修改一个微服务,修改网关路由配置之后,通常需要重启网关之后,路由配置才会生效,这样的影响会比较大。

考虑实现gateway的动态路由,不重启网关即可生效路由。

2、实现思路

基于nacos的配置,实现修改nacos的配置之后,通知给网关,在网关里编写逻辑,实现路由的自动刷新。

二、编码实现

1、nacos配置刷新公共类

java 复制代码
/**
 * nacos配置加载器
 */
public interface NacosPropertiesLoader {


    /**
     * 获取dataId
     */
    String getDataId();

    /**
     * 配置刷新的回调
     */
    void getConfigData(String configData);
}
java 复制代码
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.AbstractListener;
import com.alibaba.nacos.api.exception.NacosException;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

@Configuration
public class NacosConfigHandler implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {

    private final NacosConfigManager nacosConfigManager;

    List<NacosPropertiesLoader> nacosPropertiesLoaderList = new CopyOnWriteArrayList<>();

    private String groupId;


    public NacosConfigHandler(NacosConfigManager nacosConfigManager) {
        this.nacosConfigManager = nacosConfigManager;
    }


    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // 容器环境准备完毕了,加载配置
        ConfigService configService = nacosConfigManager.getConfigService();

        try {
            // 加载所有的配置,并设置监听器
            for (NacosPropertiesLoader nacosPropertiesLoader : nacosPropertiesLoaderList) {

                nacosPropertiesLoader.getConfigData(
                        configService.getConfig(nacosPropertiesLoader.getDataId(), groupId, 3000)
                );

                configService.addListener(nacosPropertiesLoader.getDataId(), groupId, new AbstractListener() {
                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        nacosPropertiesLoader.getConfigData(configInfo);
                    }
                });

            }
        } catch (NacosException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, NacosPropertiesLoader> nacosPropertiesLoaderBeans = applicationContext.getBeansOfType(NacosPropertiesLoader.class);
        if (nacosPropertiesLoaderBeans == null) {
            return;
        }
        for (NacosPropertiesLoader value : nacosPropertiesLoaderBeans.values()) {
            nacosPropertiesLoaderList.add(value);
        }

        // 从配置中读取nacos.group  nacos的groupId
        groupId = applicationContext.getEnvironment().getProperty("nacos.group");

    }
}

2、自定义RouteDefinition

java 复制代码
import com.alibaba.fastjson2.JSON;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.route.RouteDefinition;

import java.util.List;

/**
 * 自定义RouteDefinition
 */
public class MyRouteDefinition extends RouteDefinition {
    /**
     * 路由状态 0禁用 1启用
     */
    private Integer status;

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public static List<MyRouteDefinition> load(String config) {
        if (StringUtils.isEmpty(config)) {
            return null;
        }
        List<MyRouteDefinition> myRouteDefinitions = JSON.parseArray(config, MyRouteDefinition.class);
        return myRouteDefinitions;

    }

}

3、route缓存类

java 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static java.util.Collections.synchronizedMap;

/**
 * route缓存自定义
 */
@Component
public class MyInMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
    private static final Logger log = LoggerFactory.getLogger(DynamicRouteService.class);

    private Map<String, RouteDefinition> routes = synchronizedMap(new LinkedHashMap<String, RouteDefinition>());


    public void refreshRoute(List<MyRouteDefinition> routeDefinitions) {
        Map<String, RouteDefinition> newRoutes = synchronizedMap(new LinkedHashMap<String, RouteDefinition>());
        routeDefinitions.forEach(r -> newRoutes.put(r.getId(), r));
        routes = newRoutes;
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(r -> {
            if (ObjectUtils.isEmpty(r.getId())) {
                return Mono.error(new IllegalArgumentException("id may not be empty"));
            }
            routes.put(r.getId(), r);
            return Mono.empty();
        });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id -> {
            if (routes.containsKey(id)) {
                routes.remove(id);
                return Mono.empty();
            }
//            return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId)));
            log.warn("RouteDefinition not found: " + routeId);
            return Mono.empty();
        });
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        Map<String, RouteDefinition> routesSafeCopy = new LinkedHashMap<>(routes);
        return Flux.fromIterable(routesSafeCopy.values());
    }
}

4、动态更新路由网关service

java 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * 动态更新路由网关service
 * 1)实现一个Spring提供的事件推送接口ApplicationEventPublisherAware
 * 2)提供动态路由的基础方法,可通过获取bean操作该类的方法。该类提供新增路由、更新路由、删除路由,然后实现发布的功能。
 */
@Service
public class DynamicRouteService implements ApplicationEventPublisherAware {

    private static final Logger log = LoggerFactory.getLogger(DynamicRouteService.class);

    @Autowired
    private MyInMemoryRouteDefinitionRepository repository;

    /**
     * 发布事件
     */

    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    /**
     * 删除路由
     *
     * @param id
     * @return
     */
    public synchronized void delete(String id) {
        try {
            repository.delete(Mono.just(id)).subscribe();
//            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 更新路由
     *
     * @param definition
     * @return
     */
    public synchronized String update(RouteDefinition definition) {
        try {
            log.info("gateway update route {}", definition);
        } catch (Exception e) {
            return "update fail,not find route  routeId: " + definition.getId();
        }
        try {
            repository.save(Mono.just(definition)).subscribe();
//            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "success";
        } catch (Exception e) {
            return "update route fail";
        }
    }

    /**
     * 增加路由
     *
     * @param definition
     * @return
     */
    public synchronized String add(RouteDefinition definition) {
        log.info("gateway add route {}", definition);
        try {
            repository.save(Mono.just(definition)).subscribe();
//            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        } catch (Exception e) {
            log.warn(e.getMessage(),e);
        }
        return "success";
    }

    public void refreshRoutes(List<MyRouteDefinition> load) {
        repository.refreshRoute(load);
    }
}

5、动态路由加载类

java 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class DynamicRouteLoader implements NacosPropertiesLoader {

    private static final Logger log = LoggerFactory.getLogger(DynamicRouteLoader.class);


    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private DynamicRouteService dynamicRouteService;
	// nacos上配置的dataID
    private static final String DataId = "gateway_routes";

    @Override
    public String getDataId() {
        return DataId;
    }

    @Override
    public void getConfigData(String configData) {
        log.info("加载到路由配置:{}", log);
        // 动态加载路由
        List<MyRouteDefinition> load = MyRouteDefinition.load(configData);

        if (load == null || load.size() == 0) {
            log.info("未加载到routes");
            return;

        }

        dynamicRouteService.refreshRoutes(load);

        // 路由刷新事件,让路由生效
        this.applicationContext.publishEvent(new RefreshRoutesEvent(this));
    }
}

三、测试

在nacos上创建一个配置(注意dataid和group):

内容需要按照json格式进行配置(其他格式需要手写配置的解析方法)

json 复制代码
[{
	"filters": [],
	"id": "payment_routh",
	"metadata": {},
	"order": 0,
	"predicates": [{
		"args": {
			"_genkey_0": "/test/**"
		},
		"name": "Path"
	}],
	"uri": "lb://test1"
},
{
	"filters": [],
	"id": "payment_routh2",
	"metadata": {},
	"order": 0,
	"predicates": [{
		"args": {
			"_genkey_0": "/test2/**"
		},
		"name": "Path"
	}],
	"uri": "lb://test2"
}

]

修改该配置会自动更新路由信息。

相关推荐
冰帝海岸4 小时前
01-spring security认证笔记
java·笔记·spring
没书读了5 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
代码小鑫8 小时前
A043-基于Spring Boot的秒杀系统设计与实现
java·开发语言·数据库·spring boot·后端·spring·毕业设计
真心喜欢你吖8 小时前
SpringBoot与MongoDB深度整合及应用案例
java·spring boot·后端·mongodb·spring
斗-匕10 小时前
Spring事务管理
数据库·spring·oracle
天天扭码12 小时前
五天SpringCloud计划——DAY1之mybatis-plus的使用
java·spring cloud·mybatis
Doker 多克13 小时前
Spring AI 框架使用的核心概念
人工智能·spring·chatgpt
请叫我青哥16 小时前
第五十二条:谨慎使用重载
java·spring
孟秋与你17 小时前
【spring】spring单例模式与锁对象作用域的分析
java·spring·单例模式
luckywuxn18 小时前
Spring Cloud Alibaba、Spring Cloud 与 Spring Boot各版本的对应关系
spring boot·spring·spring cloud