跳到主要内容

09、SpringCloud Gateway 动态路由

SpringCloud Gateway 路由配置变更的时候,如果不重启网关,是无法生效的,本文介绍如果在不重启网关的情况下,动态刷新路由配置。

1、解决路由无法删除的问题

Spring Cloud Gateway 默认使用的基于内存为存储器的 InMemoryRouteDefinitionRepository,在GatewayAutoConfiguration中可以看到

@Bean
@ConditionalOnMissingBean(RouteDefinitionRepository.class)
public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
	return new InMemoryRouteDefinitionRepository();
}

在InMemoryRouteDefinitionRepository中我们可以看到,只有根据路由id去删除路由的操作。而要获取需要删除的路由的id,是比较复杂的。所以我们在路由变更的时候采用比较暴力的方式,即清空->重新加载。所以我们需要对InMemoryRouteDefinitionRepository进行改造。

基于此,我们重新定义了CustomInMemoryRouteDefinitionRepository。代码如下:

import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.util.ObjectUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

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

import static java.util.Collections.synchronizedMap;

/**
 * @Author: Flex.Zang
 * 解决无法删除路由的问题
 */
public class CustomInMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
    private static final String DELETE = "delete";

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

    @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 (DELETE.equals(id)) {
                routes.clear();
                return Mono.empty();
            }
            if (routes.containsKey(id)) {
                routes.remove(id);
                return Mono.empty();
            }
            return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId)));
        });
    }

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

并将默认的RouteDefinitionRepository替换成我们定义的CustomInMemoryRouteDefinitionRepository,代码如下

@Bean
public RouteDefinitionRepository routeDefinitionRepository() {
    return new CustomInMemoryRouteDefinitionRepository();
}

2、定义动态路由操作类

此处内容较简单,直接上代码

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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.Component;
import reactor.core.publisher.Mono;

/**
 * @Author: Flex.Zang
 */
@Slf4j
@Component
public class DynamicRouteService implements ApplicationEventPublisherAware {
    private final RouteDefinitionWriter routeDefinitionWriter;
    private ApplicationEventPublisher publisher;
    private static final String CONS_DELETE = "delete";

    @Autowired
    public DynamicRouteService(RouteDefinitionWriter routeDefinitionWriter) {
        this.routeDefinitionWriter = routeDefinitionWriter;
    }

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

    /**
     * 新增路由
     *
     * @param definition
     * @return
     */
    public String add(RouteDefinition definition) {
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "success";
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return "update route fail";
        }
    }

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

    /**
     * 清空路由
     *
     * @return
     */
    public String clear() {
        try {
            this.routeDefinitionWriter.delete(Mono.just(CONS_DELETE)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "success";
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return "clear route fail";
        }
    }

}

3、监听路由变更

此处内容较简单,直接上代码

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.List;

/**
 * @Author: Flex.Zang
 */
@Component
@ConfigurationProperties(prefix = "spring.cloud.gateway")
public class GatewayConfig {
    private List<RouteDefinition> routes;
    @Resource
    private DynamicRouteService dynamicRouteService;

    public void setRoutes(List<RouteDefinition> routes) {
        this.routes = routes;
        updateDefinition();
    }

    private void updateDefinition() {
        //清空路由
        dynamicRouteService.clear();
        if (!CollectionUtils.isEmpty(this.routes)) {
            //重新加载路由
            this.routes.forEach(definition -> {
                dynamicRouteService.add(definition);
            });
        }
    }
}

That's all, thank you!

版权声明:「DDKK.COM 弟弟快看,程序员编程资料站」本站文章,版权归原作者所有