Spring Cloud Gateway動態路由Apollo實現詳解
背景
在之前我們瞭解的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(); }
但是遺憾的是這兩種方式都不支持動態路由,都需要重啟服務。 所以我們需要對Spring Cloud Gateway
進行改造,在改造的時候我們就需要看看源碼瞭解下Spring Cloud Gateway
的路由加載
路由的加載
我們之前分析瞭路由的加載主要在GatewayAutoConfiguration
的routeDefinitionRouteLocator
方法加載的
實際上最終獲取的路由信息都是在GatewayProperties
這個配置類中
所以我們在動態路由的時候修改GatewayProperties
中的屬性即可,即
List<RouteDefinition> routes
List<FilterDefinition> defaultFilters
恰巧Spring Cloud Gateway
也提供瞭相應的get
、set
方法
實際如果我們修改瞭該屬性我們會發現並不會立即生效,因為我們會發現還有一個RouteLocator
就是CachingRouteLocator
,並且在配置Bean的時候加瞭註解@Primary
,說明最後使用額RouteLocator
實際是CachingRouteLocator
CachingRouteLocator
最後還是使用RouteDefinitionRouteLocator
類加載的,也是就我們上面分析的,看CachingRouteLocator
就知道是緩存作用
這裡引用網上一張加載圖片
參考https://www.jb51.net/article/219238.htm
所以看到這裡我們知道我們還需要解決的一個問題就是更新緩存,如何刷新緩存呢,這裡Spring Cloud Gateway
利用spring的事件機制給我提供瞭擴展
所以我們要做的事情就是這兩件事:
GatewayProperties
- 刷新緩存
實現動態路由
這裡代碼參考 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時不會銷毀當前對象 * 如果把spring.cloud.gateway.前綴的配置項全部刪除(例如需要動態刪除最後一個路由的場景),initializeBean時也無法創建新的bean,則return當前bean * 若仍保留有spring.cloud.gateway.routes[n]或spring.cloud.gateway.default-filters[n]等配置,initializeBean時會註入新的屬性替換已有的bean * 這個方法提供瞭類似@PreDestroy的操作,根據配置文件的實際情況把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!"); } /*** * 根據changeEvent和定義的pattern匹配key,如果所有對應PropertyChangeType為DELETED則需要清空GatewayProperties裡相關集合 * * @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
配置內容如下:
spring: cloud: gateway: routes: - id: test predicates: - Path=/ms/test/* filters: - StripPrefix=2 uri: http://localhost:9000
然後我們可以通過訪問地址: http:localhost:8080/ms/test/health
看刪除後是否是404,加上後是否可以正常動態路由
值得註意的是上面@ApolloConfigChangeListener
中如果沒有添加新的namespace
,value
可以不用填寫,如果配置文件是yml配置文件,在監聽的時候需要指定文件後綴
以上就是Spring Cloud Gateway動態路由Apollo實現詳解的詳細內容,更多關於Spring Cloud Gateway Apollo的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- spring-cloud-gateway動態路由的實現方法
- 深入剖析網關gateway原理
- SpringCloud超詳細講解微服務網關Gateway
- SpringBoot整合Apollo配置中心快速使用詳解
- spring cloud gateway轉發服務報錯的解決