1:配置相关nacos信息
其他信息已省略
项目中gateway.yml
nacos:
server-addr:xx.xx.xx.xx:8848
namespace: dev
#省略其他代码。。。。。。。。。
#动态路由相关配置
dynamic:
route:
data-id: routes-${spring.application.name}.yaml
group: ROUTE_GROUP
server-addr: xx.xx.xx.xx:8848
namespace: dev
2:创建相关实体类
过滤器
package com.carry.www.entity;
/**
* 过滤器实体类
*
*/
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
@Data
public class FilterEntity {
// 过滤器对应的Name
private String name;
// 路由规则
private Map<String, String> args = new LinkedHashMap<>();
}
断言:
package com.carry.www.entity;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 路由断言实体类
*
*/
@Data
public class PredicateEntity {
// 断言对应的Name
private String name;
// 断言规则
private Map<String, String> args = new LinkedHashMap<>();
}
路由类
package com.carry.www.entity;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 路由实体类
*
*/
@Data
public class RouteEntity {
// 路由id
private String id;
// 路由断言集合
private List<PredicateEntity> predicates = new ArrayList<>();
// 路由过滤器集合
private List<FilterEntity> filters = new ArrayList<>();
// 路由转发的目标uri
private String uri;
// 路由执行的顺序
private int order = 0;
}
3:添加监听
监听nacos配置信息
package com.carry.www.config;
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.Listener;
import com.carry.www.entity.FilterEntity;
import com.carry.www.entity.PredicateEntity;
import com.carry.www.entity.RouteEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;
/**
* 从Nacos获取动态路由
* 实现ApplicationEventPublisherAware发布接口来发布路由更新事件
*/
@Configuration
@Slf4j
public class DynamicRoutingConfig implements ApplicationEventPublisherAware {
@Value("${dynamic.route.server-addr}")
private String serverAddr;
@Value("${nacos.namespace}")
private String namespace;
@Value("${dynamic.route.data-id}")
private String dataId;
@Value("${dynamic.route.group}")
private String groupId;
// 保存、删除路由服务
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/**
* @return
* @Author carryer
* @Description 获取nacos配置服务
* @Date
* @Param
**/
public ConfigService getNacosConfigInfo() throws Exception {
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.SERVER_ADDR, serverAddr);
// properties.setProperty(PropertyKeyConst.USERNAME, username);
// properties.setProperty(PropertyKeyConst.PASSWORD, password);
properties.setProperty(PropertyKeyConst.NAMESPACE, namespace);
ConfigService configService = NacosFactory.createConfigService(properties);
return configService;
}
/**
* @return
* @Author carryer
* @Description 初始化路由
* @Date
* @Param
**/
@Bean
public void initRouting() {
try {
ConfigService configService = this.getNacosConfigInfo();
String configInfo = configService.getConfig(dataId, groupId, 5000);
if (null != configInfo) {
List<RouteEntity> list = JSONObject.parseArray(configInfo).toJavaList(RouteEntity.class);
for (RouteEntity route : list) {
update(assembleRouteDefinition(route));
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
e.printStackTrace();
}
}
/**
* @return
* @Author carryer
* @Description 刷新路由
* @Date
* @Param
**/
@Bean
public void refreshRouting() {
try {
ConfigService configService = this.getNacosConfigInfo();
//监听路由变化
configService.addListener(
dataId,
groupId,
new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
try {
log.info(configInfo);
if (null != configInfo) {
List<RouteEntity> list =
JSONObject.parseArray(configInfo).toJavaList(RouteEntity.class);
//更新路由表
for (RouteEntity route : list) {
update(assembleRouteDefinition(route));
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
e.printStackTrace();
}
}
});
} catch (Exception e) {
log.error(e.getMessage(), e);
e.printStackTrace();
}
}
/**
* @return
* @Author carryer
* @Description 路由更新
* @Date
* @Param
**/
private void update(RouteDefinition routeDefinition) throws Exception {
//先删除路由
routeDefinitionWriter.delete(Mono.just(routeDefinition.getId()));
//再保存路由
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
//发布事件 发布者是RefreshRoutesEvent 事件是刷新路由
applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
}
/**
* @return
* @Author carryer
* @Description 实体信息解析
* @Date
* @Param
**/
private RouteDefinition assembleRouteDefinition(RouteEntity routeEntity) {
RouteDefinition definition = new RouteDefinition();
// ID
definition.setId(routeEntity.getId());
// Predicates断言
List<PredicateDefinition> pdList = new ArrayList<>();
for (PredicateEntity predicateEntity : routeEntity.getPredicates()) {
PredicateDefinition predicateDefinition = new PredicateDefinition();
predicateDefinition.setArgs(predicateEntity.getArgs());
predicateDefinition.setName(predicateEntity.getName());
pdList.add(predicateDefinition);
}
definition.setPredicates(pdList);
// Filters过滤器
List<FilterDefinition> fdList = new ArrayList<>();
for (FilterEntity filterEntity : routeEntity.getFilters()) {
FilterDefinition filterDefinition = new FilterDefinition();
filterDefinition.setArgs(filterEntity.getArgs());
filterDefinition.setName(filterEntity.getName());
fdList.add(filterDefinition);
}
definition.setFilters(fdList);
// URI
URI uri = UriComponentsBuilder.fromUriString(routeEntity.getUri()).build().toUri();
definition.setUri(uri);
return definition;
}
}
以上代码nacos即可动态添加路由,小伙伴们可以直接使用
=============================分割线=============================================
4:相关源码解析
上面的配置,首先加载api-gatway.yaml里的信息,然后加载routes-api-gatway.yaml里的配置信息,原理都是一样的,只不过监听的是routes-api-gatway.yaml(里面是json格式)
ApplicationEventPublisherAware是一个事件发布接口,包括事件、发布者、监听者。这里事件的发布者是网关自带的类,RefreshRoutesEvent,事件是刷新路由。
不管初始化路由还是刷新路由,第一步都需要获取nacos的相关信息,ConfigService由静态工厂类NacosFactory获取
进入ConfigFactory发现ConfigService是由反射机制获取的NacosConfigService,NacosConfigService实现了ConfigService
进入NacosConfigService,里面的构造方法进行了nacos的相关赋值,包括命名空间之类的,至此可以看做已经成功连接了nacos并且返回了nacos的配置类。
下面看第二句代码 String configInfo = configService.getConfig(dataId, groupId, 5000) 这句话的意思其实就是nacos配置类根据dataId和groupId获取配置文件信息,也就是你在nacos可视化界面里面配置的yaml内容。
下面看代码
直接看getConfigInner方法,这个方法优先获取本地的Nacos配置信息,如果没有,则获取服务器上的配置信息
String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
获取nacos服务器上配置信息。
String[] ct = worker.getServerConfig(dataId, group, tenant, timeoutMs);
Nacos的ClientWorker类进行赋值操作,调用getServerConfig方法获取配置返回配置数组
进入ClientWorker的getServerConfig
ClientWorker里面核心方法 agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout),点击进入ServerHttpAgent
ServerHttpAgent类进行http调用
ServerHttpAgent里又调用了HttpSimpleClient类,进行调用 Nacos Open API 向 Nacos Server 获取配置
第二次这里的url是:http://xx.xx.xx.xx:8848/nacos/v1/cs/configs?dataId=routes-api-gatway&group=ROUTE_GROUP&tenant=dev,你用postman也可以调通,其实返回的就是你在nacos中的api-gatway.yaml或routes-api-gatway.yaml的配置文件
nacos中的routes-api-gatway.yaml的配置文件
至此,前面2行的代码已经执行完毕。下面就是进行解析然后发布事件。
String configInfo = configService.getConfig(dataId, groupId, 5000)方法返回的路由字符串,解析成路由实体类集合后,循环解析路由赋值给RouteDefinition类,
RouteDefinition里进行路由、断言、url等设置
下面验证,新加一个id是service4的路由然后发布,会发现代码自动读取了最新配置并且进行了路由解析更新了路由