Spring?Cloud?Gateway動態(tài)路由Apollo實現(xiàn)詳解
背景
在之前我們了解的Spring Cloud Gateway配置路由方式有兩種方式
- 通過配置文件
spring:
cloud:
gateway:
routes:
- id: test
predicates:
- Path=/ms/test/*
filters:
- StripPrefix=2
uri: http://localhost:9000
- 通過JavaBean
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(r -> r.path("/ms/test/**")
.filters(f -> f.stripPrefix(2))
.uri("http://localhost:9000"))
.build();
}
但是遺憾的是這兩種方式都不支持動態(tài)路由,都需要重啟服務(wù)。 所以我們需要對Spring Cloud Gateway進行改造,在改造的時候我們就需要看看源碼了解下Spring Cloud Gateway的路由加載
路由的加載
我們之前分析了路由的加載主要在GatewayAutoConfiguration的routeDefinitionRouteLocator方法加載的

實際上最終獲取的路由信息都是在GatewayProperties這個配置類中


所以我們在動態(tài)路由的時候修改GatewayProperties中的屬性即可,即
List<RouteDefinition> routes
List<FilterDefinition> defaultFilters
恰巧Spring Cloud Gateway也提供了相應(yīng)的get、set方法

實際如果我們修改了該屬性我們會發(fā)現(xiàn)并不會立即生效,因為我們會發(fā)現(xiàn)還有一個RouteLocator就是CachingRouteLocator,并且在配置Bean的時候加了注解@Primary,說明最后使用額RouteLocator實際是CachingRouteLocator

CachingRouteLocator最后還是使用RouteDefinitionRouteLocator類加載的,也是就我們上面分析的,看CachingRouteLocator就知道是緩存作用

這里引用網(wǎng)上一張加載圖片

參考http://www.dbjr.com.cn/article/219238.htm
所以看到這里我們知道我們還需要解決的一個問題就是更新緩存,如何刷新緩存呢,這里Spring Cloud Gateway利用spring的事件機制給我提供了擴展


所以我們要做的事情就是這兩件事:
GatewayProperties- 刷新緩存
實現(xiàn)動態(tài)路由
這里代碼參考 github.com/apolloconfi…
@Component
@Slf4j
public class GatewayPropertiesRefresher implements ApplicationContextAware, ApplicationEventPublisherAware {
private static final String ID_PATTERN = "spring\\.cloud\\.gateway\\.routes\\[\\d+\\]\\.id";
private static final String DEFAULT_FILTER_PATTERN = "spring\\.cloud\\.gateway\\.default-filters\\[\\d+\\]\\.name";
private ApplicationContext applicationContext;
private ApplicationEventPublisher publisher;
@Autowired
private GatewayProperties gatewayProperties;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
@ApolloConfigChangeListener(value = "route.yml",interestedKeyPrefixes = "spring.cloud.gateway.")
public void onChange(ConfigChangeEvent changeEvent) {
refreshGatewayProperties(changeEvent);
}
/***
* 刷新org.springframework.cloud.gateway.config.PropertiesRouteDefinitionLocator中定義的routes
*
* @param changeEvent
* @return void
* @author ksewen
* @date 2019/5/21 2:13 PM
*/
private void refreshGatewayProperties(ConfigChangeEvent changeEvent) {
log.info("Refreshing GatewayProperties!");
preDestroyGatewayProperties(changeEvent);
this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
refreshGatewayRouteDefinition();
log.info("GatewayProperties refreshed!");
}
/***
* GatewayProperties沒有@PreDestroy和destroy方法
* org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#rebind(java.lang.String)中destroyBean時不會銷毀當(dāng)前對象
* 如果把spring.cloud.gateway.前綴的配置項全部刪除(例如需要動態(tài)刪除最后一個路由的場景),initializeBean時也無法創(chuàng)建新的bean,則return當(dāng)前bean
* 若仍保留有spring.cloud.gateway.routes[n]或spring.cloud.gateway.default-filters[n]等配置,initializeBean時會注入新的屬性替換已有的bean
* 這個方法提供了類似@PreDestroy的操作,根據(jù)配置文件的實際情況把org.springframework.cloud.gateway.config.GatewayProperties#routes
* 和org.springframework.cloud.gateway.config.GatewayProperties#defaultFilters兩個集合清空
*
* @param
* @return void
* @author ksewen
* @date 2019/5/21 2:13 PM
*/
private synchronized void preDestroyGatewayProperties(ConfigChangeEvent changeEvent) {
log.info("Pre Destroy GatewayProperties!");
final boolean needClearRoutes = this.checkNeedClear(changeEvent, ID_PATTERN, this.gatewayProperties.getRoutes()
.size());
if (needClearRoutes) {
this.gatewayProperties.setRoutes(new ArrayList<>());
}
final boolean needClearDefaultFilters = this.checkNeedClear(changeEvent, DEFAULT_FILTER_PATTERN, this.gatewayProperties.getDefaultFilters()
.size());
if (needClearDefaultFilters) {
this.gatewayProperties.setDefaultFilters(new ArrayList<>());
}
log.info("Pre Destroy GatewayProperties finished!");
}
private void refreshGatewayRouteDefinition() {
log.info("Refreshing Gateway RouteDefinition!");
this.publisher.publishEvent(new RefreshRoutesEvent(this));
log.info("Gateway RouteDefinition refreshed!");
}
/***
* 根據(jù)changeEvent和定義的pattern匹配key,如果所有對應(yīng)PropertyChangeType為DELETED則需要清空GatewayProperties里相關(guān)集合
*
* @param changeEvent
* @param pattern
* @param existSize
* @return boolean
* @author ksewen
* @date 2019/5/23 2:18 PM
*/
private boolean checkNeedClear(ConfigChangeEvent changeEvent, String pattern, int existSize) {
return changeEvent.changedKeys().stream().filter(key -> key.matches(pattern))
.filter(key -> {
ConfigChange change = changeEvent.getChange(key);
return PropertyChangeType.DELETED.equals(change.getChangeType());
}).count() == existSize;
}
}
然后我們在apollo添加namespace:route.yml
配置內(nèi)容如下:
spring:
cloud:
gateway:
routes:
- id: test
predicates:
- Path=/ms/test/*
filters:
- StripPrefix=2
uri: http://localhost:9000
然后我們可以通過訪問地址: http:localhost:8080/ms/test/health
看刪除后是否是404,加上后是否可以正常動態(tài)路由
值得注意的是上面@ApolloConfigChangeListener中如果沒有添加新的namespace,value可以不用填寫,如果配置文件是yml配置文件,在監(jiān)聽的時候需要指定文件后綴
以上就是Spring Cloud Gateway動態(tài)路由Apollo實現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于Spring Cloud Gateway Apollo的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Netty解碼器LengthFieldBasedFrameDecoder詳解
這篇文章主要介紹了Netty解碼器LengthFieldBasedFrameDecoder的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-05-05
Java連接sftp服務(wù)器實現(xiàn)上傳下載功能
這篇文章主要介紹了java連接sftp服務(wù)器實現(xiàn)上傳下載,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-08-08
SpringBoot項目啟動報錯"找不到或無法加載主類"的解決方法
在使用 IntelliJ IDEA 開發(fā)基于 Spring Boot 框架的 Java 程序時,可能會出現(xiàn)找不到或無法加載主類 com.example.springboot.SpringbootApplication的錯誤提示,下面我們來看看如何解決吧2025-03-03

