Spring Cloud Gateway官方教程讲的都是提早在配置文件中配置网关,实际项目中,Spring Cloud Gateway做为微服务的入口,须要尽可能避免重启,因此咱们须要在Spring Cloud Gateway运行时动态配置网关。html
Spring Cloud Gateway在2018年6月份发布了2.0第一个release版本,官方文档并无讲如何动态配置,翻看Spring Cloud Gateway源码,发现类org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint中提供了网关配置Restful接口,默认没有启用。在类org.springframework.cloud.gateway.config.GatewayAutoConfiguration中配置了GatewayControllerEndpointjava
@Configuration @ConditionalOnClass(Health.class) protected static class GatewayActuatorConfiguration { @Bean @ConditionalOnEnabledEndpoint public GatewayControllerEndpoint gatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters, List<GatewayFilterFactory> GatewayFilters, RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) { return new GatewayControllerEndpoint(routeDefinitionLocator, globalFilters, GatewayFilters, routeDefinitionWriter, routeLocator); } }
存在org.springframework.boot.actuate.health.Health时启用,添加actuator依赖:react
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
在gateway项目的application.yml中启用gateway api:web
#开启actuator管理api,后面要关闭 management: endpoints: web: exposure: include: "*"
访问 http://localhost:网关端口/actuator/gateway/routes,返回了当前网关的路由信息。redis
不过这里咱们不打算用自带的Restful接口,一来官方文档也没说新增网关参数怎么传,再者咱们也不但愿在网关暴露这些接口。参照org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint,咱们本身编程来动态改变网关。spring
直接上代码:编程
import java.net.URI; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.event.RefreshRoutesEvent; 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.stereotype.Service; import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Mono; @Service public class TestGatewayService implements ApplicationEventPublisherAware { @Autowired private RouteDefinitionWriter routeDefinitionWriter; private ApplicationEventPublisher publisher; public String save() { RouteDefinition definition = new RouteDefinition(); PredicateDefinition predicate = new PredicateDefinition(); Map<String, String> predicateParams = new HashMap<>(8); definition.setId("baiduRoute"); predicate.setName("Path"); //请替换成本地可访问的路径 predicateParams.put("pattern", "/baidu"); //请替换成本地可访问的路径 predicateParams.put("pathPattern", "/baidu"); predicate.setArgs(predicateParams); definition.setPredicates(Arrays.asList(predicate)); //请替换成本地可访问的域名 URI uri = UriComponentsBuilder.fromHttpUrl("http://www.baidu.com").build().toUri(); definition.setUri(uri); routeDefinitionWriter.save(Mono.just(definition)).subscribe(); this.publisher.publishEvent(new RefreshRoutesEvent(this)); return "success"; } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.publisher = applicationEventPublisher; } }
代码说明:json
一、predicate.setName("Path"); 设置predicat名称,这个名称不是乱起的,Spring会根据名称去查找对应的FilterFactory,目前支持的名称有:After、Before、Between、Cookie、Header、Host、Method、Path、Query、RemoteAddr。api
对应官方文档的Route Predicate Factories(http://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.0.0.RELEASE/single/spring-cloud-gateway.html)app
二、definition.setId("baiduRoute"); 设置definition的id,须要全局惟一,默认使用UUID。
三、predicateParams.put("pattern", "/baidu"); 每一个Route Predicate的参数不一样,详情在官网文档查看对应的Route Predicate配置示例,然而官方文档也很坑,好比Path Route的- Path=/foo/{segment},把参数给省略了。仍是得看源码,在包org.springframework.cloud.gateway.handler.predicate里有Spring Cloud Gateway全部的Predicate,打开对应的RoutePredicateFactory,内部类Config就是该Predicate支持的参数。
四、routeDefinitionWriter.save(Mono.just(definition)).subscribe(); 默认的RouteDefinitionWriter实现类是org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository。注意最后必定要调用subscribe(),不然不执行。
至此已编码动态配置了一个基本的网关。
带Filter的配置代码:
RouteDefinition routeDefinition = new RouteDefinition(); PredicateDefinition predicateDefinition = new PredicateDefinition(); Map<String, String> predicateParams = new HashMap<>(8); Map<String, String> filterParams = new HashMap<>(8); FilterDefinition filterDefinition = new FilterDefinition(); URI uri = UriComponentsBuilder.fromUriString("lb://HELLO-SERVICE").build().toUri(); routeDefinition.setId("rateLimitTest"); // 名称是固定的,spring gateway会根据名称找对应的PredicateFactory predicateDefinition.setName("Path"); predicateParams.put("pattern", "/rate/**"); predicateDefinition.setArgs(predicateParams); // 名称是固定的,spring gateway会根据名称找对应的FilterFactory filterDefinition.setName("RequestRateLimiter"); // 每秒最大访问次数 filterParams.put("redis-rate-limiter.replenishRate", "2"); // 令牌桶最大容量 filterParams.put("redis-rate-limiter.burstCapacity", "3"); // 限流策略(#{@BeanName}) filterParams.put("key-resolver", "#{@hostAddressKeyResolver}"); // 自定义限流器(#{@BeanName}) //filterParams.put("rate-limiter", "#{@redisRateLimiter}"); filterDefinition.setArgs(filterParams); routeDefinition.setPredicates(Arrays.asList(predicateDefinition)); routeDefinition.setFilters(Arrays.asList(filterDefinition)); routeDefinition.setUri(uri); routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe(); publisher.publishEvent(new RefreshRoutesEvent(this));
须要注意的是filterParams.put("key-resolver", "#{@hostAddressKeyResolver}"); hostAddressKeyResolver是我自定义的Spring Bean,#{@BeanName}是Spring的表达式,用来注入Bean。
Spring Cloud Gateway默认的RouteDefinitionWriter实现类是org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository,Route信息保存在当前实例的内存中,这在集群环境中会存在同步问题。咱们能够自定义一个基于Redis的RouteDefinitionWriter。
import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.cloud.gateway.route.RouteDefinitionRepository; import org.springframework.cloud.gateway.support.NotFoundException; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSON; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** * 使用Redis保存自定义路由配置(代替默认的InMemoryRouteDefinitionRepository) * <p/> * 存在问题:每次请求都会调用getRouteDefinitions,当网关较多时,会影响请求速度,考虑放到本地Map中,使用消息通知Map更新。 * * @since 2018年7月9日 下午2:39:02 */ @Component public class RedisRouteDefinitionRepository implements RouteDefinitionRepository { public static final String GATEWAY_ROUTES = "geteway_routes"; @Autowired private StringRedisTemplate redisTemplate; @Override public Flux<RouteDefinition> getRouteDefinitions() { List<RouteDefinition> routeDefinitions = new ArrayList<>(); redisTemplate.opsForHash().values(GATEWAY_ROUTES).stream().forEach(routeDefinition -> { routeDefinitions.add(JSON.parseObject(routeDefinition.toString(), RouteDefinition.class)); }); return Flux.fromIterable(routeDefinitions); } @Override public Mono<Void> save(Mono<RouteDefinition> route) { return route .flatMap(routeDefinition -> { redisTemplate.opsForHash().put(GATEWAY_ROUTES, routeDefinition.getId(), JSON.toJSONString(routeDefinition)); return Mono.empty(); }); } @Override public Mono<Void> delete(Mono<String> routeId) { return routeId.flatMap(id -> { if (redisTemplate.opsForHash().hasKey(GATEWAY_ROUTES, id)) { redisTemplate.opsForHash().delete(GATEWAY_ROUTES, id); return Mono.empty(); } return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId))); }); } }