一. 概述
网关路由的核心类CachingRouteLocator, 它缓存了所有的路由,每个请求都需要从该类获取所有路由,且该类是一个spring事件处理器,实现刷新路由的事件,清空CachingRouteLocator的路由缓存,而缓存方法CacheFlux.lookup当缓存被清空的时候会自动重新从InMemoryRouteDefinitionRepository加载缓存。所以实现nacos动态路由只需要两步,第一步添加nacos配置变更监听器,第二步在nacos配置变更监听器中使用RouteDefinitionWriter更新路由信息,同时发送spring路由刷新事件。
二. pom.xml添加nacos依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
三. 添加配置
application.yml添加动态路由文件配置
四. 添加配置属性类
import com.sungrow.gateway.config.DynamicRouteConfig;
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @Description 加载动态路由配置,通过此配置加载配置文件 gateway-dynamic-dev.json 里的路由信息
**/
@Data
@Configuration
@ConfigurationProperties(prefix = "dynamic.route")
@ConditionalOnBean( DynamicRouteConfig.class)
public class DynamicRouteProperties {
/**
* nacos 配置管理 dataId
*/
private String dataId;
/**
* nacos 配置管理 group
*/
private String group;
/**
* nacos 服务地址
*/
private String serverAddr;
/**
* 启动动态路由的标志,默认关闭
*/
private boolean enabled = false;
private String namespace;
private String username;
private String password;
}
五. 添加路由操作接口
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
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 java.util.List;
@Slf4j
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
/**
* 增加路由
* @param definition
* @return
*/
public void add(RouteDefinition definition) {
try {
addFilter(definition);
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
} catch (Exception ex) {
log.error(String.format("add -> save route fail【routeId: %s】", definition.getId()), ex);
}
}
/**
* 更新路由
* @param definition
* @return
*/
public void update(RouteDefinition definition){
try {
this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
} catch (Exception ex) {
log.error(String.format("update -> update route fail【routeId: %s】", definition.getId()), ex);
}
try {
addFilter(definition);
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
} catch (Exception ex) {
log.error(String.format("update -> save route fail【routeId: %s】", definition.getId()), ex);
}
}
/**
* 删除路由
* @param id
* @return
*/
public void delete(String id) {
try {
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
} catch (Exception ex) {
log.error(String.format("delete -> delete route fail【routeId: %s】", id), ex);
}
}
/**
* 给路由添加过滤器
*/
private void addFilter(RouteDefinition definition) {
FilterDefinition filterDefinition = new FilterDefinition();
filterDefinition.setName(GatewayLimitFilterFactory.class.getSimpleName());
List<FilterDefinition> filters = definition.getFilters();
if (CollectionUtils.isEmpty(filters)) {
filters = Lists.newArrayList();
}
filters.add(filterDefinition);
definition.setFilters(filters);
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
@Component
public class GatewayLimitFilterFactory extends AbstractGatewayFilterFactory<Object> {
@Override
public GatewayFilter apply(Object config) {
return new GatewayLimitFilter(5, 1, 5);
}
}
六. 路由配置类
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.AbstractListener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* @Description 核心配置
**/
@Slf4j
@Configuration
@ConditionalOnProperty(name = "dynamic.route.enabled", matchIfMissing = true)
public class DynamicRouteConfig {
private static final List<String> ROUTE_LIST = new ArrayList<>();
@Autowired//依赖注入,先初始化完成
private DynamicRouteProperties properties;
@Autowired
private DynamicRouteServiceImpl dynamicRouteService;
@PostConstruct
private void init() {
dynamicRouteByNacosListener();
}
private void dynamicRouteByNacosListener() {
try {
Properties prop = new Properties();
// prop.setProperty(PropertyKeyConst.NAMESPACE, properties.getNamespace());
prop.setProperty(PropertyKeyConst.SERVER_ADDR, properties.getServerAddr());
// prop.setProperty(PropertyKeyConst.USERNAME, properties.getUsername());
// prop.setProperty(PropertyKeyConst.PASSWORD, properties.getPassword());
ConfigService configService = NacosFactory.createConfigService(prop);
String content = configService.getConfigAndSignListener(
properties.getDataId(),
"DEFAULT_GROUP",
5000,
new AbstractListener() {
@Override
public void receiveConfigInfo(String configInfo) {
statsConfig(configInfo);
}
});
statsConfig(content);
} catch (NacosException e) {
log.error("nacos 获取动态路由配置和监听异常", e);
}
}
private void statsConfig(String configInfo) {
try {
clearRoute();
List<RouteDefinition> gatewayRouteDefinitions = JSONObject.parseArray(configInfo, RouteDefinition.class);
for (RouteDefinition routeDefinition : gatewayRouteDefinitions) {
addRoute(routeDefinition);
}
} catch (Exception e) {
log.error("statsConfig异常", e);
}
}
private void clearRoute() {
for(String id : ROUTE_LIST) {
dynamicRouteService.delete(id);
}
ROUTE_LIST.clear();
}
private void addRoute(RouteDefinition definition) {
dynamicRouteService.add(definition);
ROUTE_LIST.add(definition.getId());
}
}