SpringGateway 动态路由

SpringGateway 动态路由

文章根据大佬博客合成,再写一遍怕他们删除我找不到
网关动态路由实现
路由源码解析
RewritePath

1 背景介绍

常用的网关路由配置一般有两种:
(1)配置在yaml文件里,修改时通过nacos修改,能修改,但是路由数据源只能来自文件
(2)配置在代码里,修改只能修改源码
需要一种非常灵活的修改,路由信息存在一个地方(数据库任何能取到的地方),需要修改时只需要修改数据源。

2 代码展示

参考:网关动态路由实现

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

import javax.annotation.PostConstruct;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * 动态路由配置类
 */
@Service
@Slf4j
public class DynamicRouteConfig implements ApplicationEventPublisherAware {

    @Autowired
    private RouteDefinitionWriter routedefinitionWriter;

    private ApplicationEventPublisher publisher;

    private final List<String> ROUTE_LIST = new LinkedList<>();

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

    @PostConstruct
    public void dynamicRouteListener() {
        try {
            log.info("dynamic route action...");
            publisher();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 发布路由
     */
    private void publisher() {
        // 先清除路由
        clearRoute();
        try {
            // 路由定义列表,自定义路由可以在这里被装填进这个列表 下面用假数据做个示范
            List<RouteDefinition> routeDefinitions = new ArrayList<>();
            // 单个路由
            RouteDefinition routeDefinition = new RouteDefinition();
            routeDefinition.setId("champagne-user-dev");
            routeDefinition.setUri(new URI("www.baidu.com"));
            // 过滤定义
            List<FilterDefinition> filterDefinitions = new ArrayList<>();
            filterDefinitions.add(new FilterDefinition("Path=/baidu/**"));
            routeDefinition.setFilters(filterDefinitions);
            // 断言定义
            List<PredicateDefinition> predicateDefinitions = new ArrayList<>();
            predicateDefinitions.add(new PredicateDefinition("RewritePath=/baidu(?<segment>/?.*), $\\{segment}"));
            routeDefinition.setPredicates(predicateDefinitions);

            routeDefinitions.add(routeDefinition);

            for (RouteDefinition definition : routeDefinitions) {
                addRoute(definition);
            }
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

    }

    /**
     * 添加路由
     *
     * @param def
     * @return
     */
    public Boolean addRoute(RouteDefinition def) {
        try {
            routedefinitionWriter.save(Mono.just(def)).subscribe();
            ROUTE_LIST.add(def.getId());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 清空路由
     *
     * @return
     */
    public Boolean clearRoute() {
        for (String id : ROUTE_LIST) {
            routedefinitionWriter.delete(Mono.just(id)).subscribe();
        }
        ROUTE_LIST.clear();
        return Boolean.FALSE;
    }
}

3 具体解析

3.1 源码解析

参考:路由源码解析

3.1.1 InMemoryRouteDefinitionRepository 内存路由定义仓库

在这里插入图片描述
实现了RouteDefinitionRepository接口,RouteDefinitionRepository分别实现了RouteDefinitionLocator和RouteDefinitionWriter

3.1.2 RouteDefinitionLocator 路由定义定位器

定义了获取路由的方法

public interface RouteDefinitionLocator {
	
	Flux<RouteDefinition> getRouteDefinitions();

}
3.1.3 RouteDefinitionWriter 路由定义编写器

定义了保存和删除路由的方法

public interface RouteDefinitionWriter {

	Mono<Void> save(Mono<RouteDefinition> route);

	Mono<Void> delete(Mono<String> routeId);

}
3.1.4 AbstractGatewayControllerEndpoint

定义了修改,保存路由的对外接口 这里可以看到官方如何使用RouteDefinitionRepository的方法

//添加路由
@PostMapping("/routes/{id}")
@SuppressWarnings("unchecked")
public Mono<ResponseEntity<Object>> save(@PathVariable String id,
		@RequestBody RouteDefinition route) {

	return Mono.just(route).filter(this::validateRouteDefinition)
			.flatMap(routeDefinition -> this.routeDefinitionWriter
					.save(Mono.just(routeDefinition).map(r -> {
						r.setId(id);
						log.debug("Saving route: " + route);
						return r;
					}))
					.then(Mono.defer(() -> Mono.just(ResponseEntity
							.created(URI.create("/routes/" + id)).build()))))
			.switchIfEmpty(
					Mono.defer(() -> Mono.just(ResponseEntity.badRequest().build())));
}
// 删除路由
@DeleteMapping("/routes/{id}")
public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
	return this.routeDefinitionWriter.delete(Mono.just(id))
			.then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build())))
			.onErrorResume(t -> t instanceof NotFoundException,
					t -> Mono.just(ResponseEntity.notFound().build()));
}
// 获取所有路由
@GetMapping("/routedefinitions")
	public Flux<RouteDefinition> routesdef() {
		return this.routeDefinitionLocator.getRouteDefinitions();
	}
3.1.5 自定义路由 RouteDefinitionRepository

在网关自动配置类GatewayAutoConfiguration可以看到,默认使用InMemoryRouteDefinitionRepository实现类,我们可以重新写一个实现类替换他。

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

3.2 RewritePath解析

参考:RewritePath

前端使用不同的path访问,网关根据path断言判断后,RewritePath再次重写地址
```java
predicateDefinitions.add(new PredicateDefinition("RewritePath=/baidu(?<segment>/?.*), $\\{segment}"));

4 网关动态路由实现方式

4.1 重写RouteDefinitionRepository实现类

在项目加载时通过修改 getRouteDefinitions() 方法,再利用 save(Mono route) 保存

4.2 利用RouteDefinitionWriter对原有路由列表修改

利用 save(Mono route) 和 delete(Mono routeId) 对生成好的路由进行修改

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值