1. 原理
SpringCloud Gateway中路默认实现是 InMemoryRouteDefinitionRepository
,这里面的核心是 routeDefinitionWriter
这个对象,而 RouteDefinitionWriter
唯一的继承是RouteDefinitionRepository
类, RouteDefinitionRepository
唯一的继承是 InMemoryRouteDefinitionRepository
, 在 InMemoryRouteDefinitionRepository
中有这样的一段代码
接着往下看,在 GatewayAutoConfiguration
自动配置类中,有这样的一段代码,当RouteDefinitionRepository
没有自定义的实现的时候,所以会默认创建了 InMemoryRouteDefinitionRepository
这个Bean。
从而我们知道,Gateway中路由的默认存储方式是由 InMemoryRouteDefinitionRepository
方式实现,因此我们只要通过
RouteDefinitionWriter
对象即可实现路由的添加与删除,但是因为是存储在内存中,应用重启会丢失路由,那么我们得保证Gateway在启动的时候在外部拉取路由,这里我们存储路由用的是Nacos,通过configService对路由文件进行刷新监听。
最后我们通过实现 ApplicationEventPublisherAware
接口来发布路由刷新事件
2. 代码
GatewayConfig.java,读取nacos配置信息合路由文件data-id,后续需要对路由文件路由文件gateway-router.json进行监听
@Configuration
public class GatewayConfig {
public static final long DEFAULT_TIMEOUT = 30000;
public static String NACOS_SERVER_ADDR;
public static String NACOS_NAMESPACE;
public static String NACOS_ROUTE_DATA_ID;
public static String NACOS_ROUTE_GROUP;
@Value("${spring.cloud.nacos.config.server-addr}")
public void setNacosServerAddr(String nacosServerAddr){
NACOS_SERVER_ADDR = nacosServerAddr;
}
@Value("${spring.cloud.nacos.config.namespace}")
public void setNacosNamespace(String nacosNamespace){
NACOS_NAMESPACE = nacosNamespace;
}
@Value("${spring.cloud.nacos.config.customize.data-id}")
public void setNacosRouteDataId(String nacosRouteDataId){
NACOS_ROUTE_DATA_ID = nacosRouteDataId;
}
@Value("${spring.cloud.nacos.config.customize.group}")
public void setNacosRouteGroup(String nacosRouteGroup){
NACOS_ROUTE_GROUP = nacosRouteGroup;
}
}
DynamicRouteServiceImplByNacos.java,在这里初始化configService,对路由文件gateway-router.json进行监听,保证当更新nacos中gateway-router.json内容时,能够及时的监听变更然调用dynamicRouteService来更新路由
@Component
@Slf4j
@DependsOn({"gatewayConfig"})
public class DynamicRouteServiceImplByNacos {
@Autowired
private DynamicRouteServiceImpl dynamicRouteService;
@NacosInjected
private ConfigService configService;
@PostConstruct
public void init() {
log.info("gateway route init...");
try{
configService = initConfigService();
if(configService == null){
log.warn("initConfigService fail");
return;
}
String configInfo = configService.getConfig(GatewayConfig.NACOS_ROUTE_DATA_ID, GatewayConfig.NACOS_ROUTE_GROUP, GatewayConfig.DEFAULT_TIMEOUT);
log.info("获取网关当前配置:\r\n{}",configInfo);
List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
for(RouteDefinition definition : definitionList){
log.info("update route : {}",definition.toString());
dynamicRouteService.add(definition);
}
} catch (Exception e) {
log.error("初始化网关路由时发生错误", e);
}
dynamicRouteByNacosListener(GatewayConfig.NACOS_ROUTE_DATA_ID,GatewayConfig.NACOS_ROUTE_GROUP);
}
/**
* 监听Nacos下发的动态路由配置
*/
public void dynamicRouteByNacosListener (String dataId, String group){
try {
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
log.info("进行网关更新:\n\r{}",configInfo);
List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
for(RouteDefinition definition : definitionList){
log.info("update route : {}",definition.toString());
dynamicRouteService.update(definition);
}
}
@Override
public Executor getExecutor() {
log.info("getExecutor\n\r");
return null;
}
});
} catch (NacosException e) {
log.error("从nacos接收动态路由配置出错!!!",e);
}
}
/**
* 初始化网关路由 nacos config
*/
private ConfigService initConfigService(){
try{
Properties properties = new Properties();
properties.setProperty("serverAddr", GatewayConfig.NACOS_SERVER_ADDR);
properties.setProperty("namespace", GatewayConfig.NACOS_NAMESPACE);
return configService= NacosFactory.createConfigService(properties);
} catch (Exception e) {
log.error("初始化网关路由时发生错误",e);
return null;
}
}
}
DynamicRouteServiceImpl.java,操作路由,并且发布路由刷新事件,刷新内存中的路由信息。这里有一个很关键的代码,切记不能遗漏
routeDefinitionWriter.save(Mono.just(definition)).subscribe()
路由的save方法一定要加上subscribe(),不然路由不会生效。
@Slf4j
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
/**
* 发布事件
*/
@Autowired
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
/**
* 删除路由
* @param id
* @return
*/
public String delete(String id) {
try {
log.info("gateway delete route id {}",id);
this.routeDefinitionWriter.delete(Mono.just(id));
return "delete success";
} catch (Exception e) {
return "delete fail";
}
}
/**
* 更新路由
* @param definition
* @return
*/
public String update(RouteDefinition definition) {
try {
log.info("gateway update route {}",definition);
this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
} 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) {
return "update route fail";
}
}
/**
* 增加路由
* @param definition
* @return
*/
public String add(RouteDefinition definition) {
log.info("gateway add route {}",definition);
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
}
3. gateway-route.json文件格式
这里我分享一下自己的文件格式,根据需要自己进行修改。
[{
"filters": [{
"args": {
"parts": "2"
},
"name": "StripPrefix"
}],
"id": "app-user",
"order": 0,
"predicates": [{
"args": {
"pattern": "/v1/api-user/**"
},
"name": "Path"
}],
"uri": "lb://kinhoodcloud-app-user"
}]