SpringCloud GateWay動(dòng)態(tài)路由用法
1.網(wǎng)關(guān)為什么需要?jiǎng)討B(tài)路由?
網(wǎng)關(guān)的核心功能就是通過配置不同路由策略在配合注冊(cè)中心訪問不同的微服務(wù),而默認(rèn)是在yaml文件中配置路由策略,而在項(xiàng)目上線后,網(wǎng)關(guān)作為所有項(xiàng)目的入口肯定不希望重啟,所以動(dòng)態(tài)路由是必須的,我們?cè)谠黾右粋€(gè)服務(wù),在不希望服務(wù)重新啟動(dòng)的前提下能路由到該服務(wù),以及是基于代碼實(shí)現(xiàn)的網(wǎng)關(guān)動(dòng)態(tài)路由
2.動(dòng)態(tài)路由原理
public interface RouteDefinitionRepository
extends RouteDefinitionLocator, RouteDefinitionWriter {
}RouteDefinitionRepository是網(wǎng)關(guān)路由的存儲(chǔ)接口,RouteDefinitionLocator 是獲取存儲(chǔ)中的所有路由,RouteDefinitionWriter主要操作路由的存儲(chǔ)和刪除。
public interface RouteDefinitionLocator {
Flux<RouteDefinition> getRouteDefinitions();
}public interface RouteDefinitionWriter {
Mono<Void> save(Mono<RouteDefinition> route);
Mono<Void> delete(Mono<String> routeId);
}而gateway中RouteDefinitionRepository接口的默認(rèn)的實(shí)現(xiàn)是InMemoryRouteDefinitionRepository,即在內(nèi)存中存儲(chǔ)路由配置,而且在 GatewayAutoConfiguration 配置中也激活了InMemoryRouteDefinitionRepository這個(gè)Bean,代碼如下。
@Bean
@ConditionalOnMissingBean(RouteDefinitionRepository.class)
public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
return new InMemoryRouteDefinitionRepository();
}public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
private final Map<String, RouteDefinition> routes = synchronizedMap(
new LinkedHashMap<String, RouteDefinition>());
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap(r -> {
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)));
});
}
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(routes.values());
}
}InMemoryRouteDefinitionRepository 中可見存儲(chǔ)路由的是一個(gè)帶同步鎖的LinkedHashMap,而存儲(chǔ)刪除都是基于這個(gè)map對(duì)象操作。
3.動(dòng)態(tài)路由設(shè)計(jì)以及實(shí)現(xiàn)
- 方案一:知道動(dòng)態(tài)路由的原理以后,我們可以基于redis設(shè)計(jì)一個(gè)InRedisRouteDefinitionRepository 實(shí)現(xiàn) RouteDefinitionRepository 接口即可,即網(wǎng)關(guān)部署多個(gè)也能動(dòng)態(tài)解決路由問題
- 方案二:可以基于nacos 配置動(dòng)態(tài)修改路由(理論上,待驗(yàn)證)nacos的配置也是可以熱加載的。
@Slf4j
@Configuration("redisRouteDefinition")
@AllArgsConstructor
public class InRedisRouteDefinitionRepository implements RouteDefinitionRepository {
private RedisTemplate redisTemplate;
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap(r -> {
redisTemplate.opsForHash().put(DynamicRouterConstants.DYNAMIC_ROUTER_KEY_CONFIG, r.getId(), new Gson().toJson(r));
return Mono.empty();
});
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap(id -> {
Object router = redisTemplate.opsForHash().get(DynamicRouterConstants.DYNAMIC_ROUTER_KEY_CONFIG, id);
if (!Objects.isNull(router)) {
redisTemplate.opsForHash().delete(DynamicRouterConstants.DYNAMIC_ROUTER_KEY_CONFIG, id);
return Mono.empty();
}
return Mono.defer(() -> Mono.error(
new NotFoundException("RouteDefinition not found: " + routeId)));
});
}
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
List<String> values = redisTemplate.opsForHash().values(DynamicRouterConstants.DYNAMIC_ROUTER_KEY_CONFIG);
if (CollUtil.isNotEmpty(values)) {
List<RouteDefinition> definitions = values.stream()
.map(s -> new Gson().fromJson(s, RouteDefinition.class))
.collect(Collectors.toList());
return Flux.fromIterable(definitions);
} else {
return Flux.fromIterable(new ArrayList<>());
}
}
}暫時(shí)在網(wǎng)關(guān)中提供接口實(shí)現(xiàn)路由的動(dòng)態(tài)增加和修改Controller
@RestController
@RequestMapping("/route")
@AllArgsConstructor
public class RouteController {
private DynamicRouteService dynamicRouteService;
@PostMapping
public void saveRouteDefinition(@RequestBody GatewayRouteDefinition routeDefinition) {
dynamicRouteService.saveRouteDefinition(routeDefinition);
}
@DeleteMapping("/{id}")
public void deleteRouteDefinition(@PathVariable String id) {
dynamicRouteService.deleteRouteDefinition(id);
}
@PutMapping
public void update(@RequestBody GatewayRouteDefinition routeDefinition) {
dynamicRouteService.updateRouteDefinition(routeDefinition);
}
@GetMapping
public IPage<RouterConfig> getRouterConfigByPage(RouterConfigQueryParams params) {
return dynamicRouteService.getRouterConfigByPage(params);
}
}路由參數(shù)
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class GatewayRouteDefinition {
/**
* 路由的Id
*/
private String id;
/**
* 路由斷言集合配置
*/
private List<GatewayPredicateDefinition> predicates;
/**
* 路由過濾器集合配置
*/
private List<GatewayFilterDefinition> filters;
/**
* 路由規(guī)則轉(zhuǎn)發(fā)的目標(biāo)uri
*/
private String uri;
/**
* 路由執(zhí)行的順序
*/
private int order;
}@Data
public class GatewayPredicateDefinition implements Serializable {
/**
* 斷言對(duì)應(yīng)的Name
*/
private String name;
/**
* 配置的斷言規(guī)則
*/
private Map<String, String> args = new LinkedHashMap<>();
}@Data
public class GatewayFilterDefinition implements Serializable {
/**
* Filter Name
*/
private String name;
/**
* 對(duì)應(yīng)的路由規(guī)則
*/
private Map<String, String> args = new LinkedHashMap<>();
}業(yè)務(wù)層代碼 DynamicRouteService,最主要的是注入RouteDefinitionWriter 我們自己的實(shí)現(xiàn)類,替換默認(rèn)的配置
@Service
public class DynamicRouteServiceImpl implements DynamicRouteService {
@Resource(name = "redisRouteDefinition")
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private IRouterConfigService routerConfigService;
@Autowired
private ObjectMapper objectMapper;
@Override
public void saveRouteDefinition(GatewayRouteDefinition definition) {
// 判定當(dāng)前路由以及路徑是否存在
LambdaQueryWrapper<RouterConfig> wrapper = Wrappers.<RouterConfig>lambdaQuery()
.eq(RouterConfig::getRouterName, definition.getId())
.eq(RouterConfig::getRouterPath, definition.getUri());
List<RouterConfig> list = routerConfigService.list(wrapper);
BizVerify.verify(CollUtil.isEmpty(list), "路由已經(jīng)存在");
routerConfigService.save(paramsConvert(definition));
RouteDefinition routerDefinition = DynamicRouteUtils.convertToRouteDefinition(definition);
routeDefinitionWriter.save(Mono.just(routerDefinition)).subscribe();
}
@Override
public void updateRouteDefinition(GatewayRouteDefinition routeDefinition) {
routerConfigService.updateById(paramsConvert(routeDefinition));
RouteDefinition definition = DynamicRouteUtils.convertToRouteDefinition(routeDefinition);
deleteRouteDefinition(definition.getId());
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
}
@Override
public void deleteRouteDefinition(String routerId) {
routerConfigService.removeById(routerId);
routeDefinitionWriter
.delete(Mono.just(routerId))
.then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build())))
.onErrorResume((t) -> t instanceof NotFoundException, (t) -> Mono.just(ResponseEntity.notFound().build()));
}
private RouterConfig paramsConvert(GatewayRouteDefinition routeDefinition) {
String filterJson = null;
String PredicatesJson = null;
try {
filterJson = objectMapper.writeValueAsString(routeDefinition.getFilters());
PredicatesJson = objectMapper.writeValueAsString(routeDefinition.getPredicates());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return new RouterConfig()
.setRouterName(routeDefinition.getId())
.setRouterPath(routeDefinition.getUri())
.setRouterOrder(routeDefinition.getOrder())
.setRouterFilters(filterJson)
.setRouterPredicates(PredicatesJson);
}
@Override
public IPage<RouterConfig> getRouterConfigByPage(RouterConfigQueryParams params) {
LambdaQueryWrapper<RouterConfig> wrapper = Wrappers.<RouterConfig>lambdaQuery()
.like(StrUtil.isNotEmpty(params.getRouterName()), RouterConfig::getRouterName, params.getRouterName());
return routerConfigService.page(new Page<>(params.getPageNum(), params.getPageSize()), wrapper);
}
}4.網(wǎng)關(guān)中聚合swagger由于動(dòng)態(tài)路由引發(fā)不展示的問題
聚合swagger聚合核心代碼
package com.kill.core.provider;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
/**
* <pre>
* +--------+---------+-----------+---------+
* | |
* +--------+---------+-----------+---------+
* </pre>
*
* @author wangjian
* @since 1019/11/01 11:58:32
*/
@Component
@Primary
@AllArgsConstructor
public class SwaggerResourceProvider implements SwaggerResourcesProvider {
private static final String SWAGGER2URL = "/v2/api-docs";
private RouteDefinitionRepository repository;
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
repository.getRouteDefinitions().subscribe(
route -> {
if (CollUtil.isNotEmpty(route.getPredicates())) {
route.getPredicates().forEach(
predicateDefinition -> {
if (CollUtil.isNotEmpty(predicateDefinition.getArgs())) {
if (StrUtil.isNotEmpty(predicateDefinition.getArgs().get("pattern"))) {
resources.add(
swaggerResource(route.getId(),
predicateDefinition.getArgs().get("pattern").replace("/**", SWAGGER2URL)));
}
if (StrUtil.isNotEmpty(predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0"))) {
resources.add(
swaggerResource(route.getId(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("/**", SWAGGER2URL)));
}
}
});
}
});
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}
5.測(cè)試一下

swagger中目前只有這一個(gè)路由,調(diào)用路由新增一個(gè)

再次刷新swagger,OK 已經(jīng)看到新的路由了

redis中也已經(jīng)看到了路由的配置

6.寫在最后
不可能所有的代碼拿過來就能用,每個(gè)人的理解也不盡相同,記錄在這里希望能提供一個(gè)思路,能解決到自己遇到的問題,而不是希望大家看到后,說拷貝過來的東西都是垃圾,你可以看,如果沒有幫助到你我也很遺憾。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java性能優(yōu)化之關(guān)于大對(duì)象復(fù)用的目標(biāo)和注意點(diǎn)
這篇文章主要介紹了Java性能優(yōu)化之關(guān)于大對(duì)象復(fù)用的目標(biāo)和注意點(diǎn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
Java基于棧方式解決漢諾塔問題實(shí)例【遞歸與非遞歸算法】
這篇文章主要介紹了Java基于棧方式解決漢諾塔問題的方法,結(jié)合實(shí)例形式分析了java棧方式采用遞歸與非遞歸算法解決漢諾塔問題的相關(guān)操作技巧,需要的朋友可以參考下2017-11-11
MyBatis 如何簡(jiǎn)化的 JDBC(思路詳解)
MyBatis 是一個(gè) 持久層 框架,用于 簡(jiǎn)化JDBC開發(fā),這篇文章主要介紹了MyBatis 如何簡(jiǎn)化的 JDBC(思路詳解),需要的朋友可以參考下2023-03-03
支票金額大寫轉(zhuǎn)換示例(金額大寫轉(zhuǎn)換器)
這篇文章主要介紹了支票金額大寫轉(zhuǎn)換示例(金額大寫轉(zhuǎn)換器),需要的朋友可以參考下2014-02-02
Java之jdbc連接mysql數(shù)據(jù)庫(kù)的方法步驟詳解
這篇文章主要介紹了Java之jdbc連接mysql數(shù)據(jù)庫(kù)的方法步驟詳解,JCBC技術(shù)是java開發(fā)必備的只是,jdbc連接mysql數(shù)據(jù)庫(kù),這是一個(gè)比較簡(jiǎn)單的方法,有興趣的可以了解一下2020-07-07
Java設(shè)計(jì)模式以虹貓藍(lán)兔的故事講解裝飾器模式
裝飾器模式又名包裝(Wrapper)模式。裝飾器模式以對(duì)客戶端透明的方式拓展對(duì)象的功能,是繼承關(guān)系的一種替代方案,本篇文章以虹貓藍(lán)兔生動(dòng)形象的為你帶來詳細(xì)講解2022-04-04
springboot @WebFilter注解過濾器的實(shí)現(xiàn)
這篇文章主要介紹了springboot @WebFilter注解過濾器的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03

