我们一般在yml文件里这么配置
spring:
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能
lower-case-service-id: true #使用小写服务名,默认是大写
default-filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 2 #令牌桶的容积
redis-rate-limiter.burstCapacity: 2 #流速 每秒
key-resolver: "#{@pathKeyResolver}" #SPEL表达式去的对应的bean
rate-limiter: "#{@defaultRedisRateLimiter}" #SPEL表达式去的对应的bean
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: 'forward:/fallback'
- name: Retry
args:
retries: 3
routes:
- id: springcloud-user
uri: lb://springcloud-user
predicates:
- Path=/user/**
filters:
- ImgCodeFilter
- OauthGatewayFilter
- PasswordDecoderFilter
- id: springcloud-provider
uri: lb://springcloud-provider
predicates:
- Path=/provider/**
filters:
- AddResponseHeader=foo, bar
- id: springcloud-consumer
uri: lb://springcloud-consumer
predicates:
- Path=/consumer/**
filters:
- AddResponseHeader=foo, bar
配置文件里的routes对应下面类
@ConfigurationProperties("spring.cloud.gateway")
@Validated
public class GatewayProperties {
@NotNull
@Valid
private List<RouteDefinition> routes = new ArrayList<>();
private List<FilterDefinition> defaultFilters = new ArrayList<>();
private List<MediaType> streamingMediaTypes = Arrays.asList(MediaType.TEXT_EVENT_STREAM, MediaType.APPLICATION_STREAM_JSON);
}
public class RouteDefinition {
private String id;
@NotEmpty
@Valid
private List<PredicateDefinition> predicates = new ArrayList<>();
@Valid
private List<FilterDefinition> filters = new ArrayList<>();
@NotNull
private URI uri;
private Map<String, Object> metadata = new HashMap<>();
private int order = 0;
}
public class PredicateDefinition {
@NotNull
private String name;
private Map<String, String> args = new LinkedHashMap<>();
}
public class FilterDefinition {
@NotNull
private String name;
private Map<String, String> args = new LinkedHashMap<>();
}
下面来看下GatewayAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore({HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class})
@AutoConfigureAfter({GatewayLoadBalancerClientAutoConfiguration.class, GatewayClassPathWarningAutoConfiguration.class})
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {
@Bean
public RouteLocatorBuilder routeLocatorBuilder(ConfigurableApplicationContext context) {
return new RouteLocatorBuilder(context);
}
@Bean
@ConditionalOnMissingBean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
return new PropertiesRouteDefinitionLocator(properties);
}
@Bean
@ConditionalOnMissingBean(RouteDefinitionRepository.class)
public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
return new InMemoryRouteDefinitionRepository();
}
}
从上面GatewayAutoConfiguration代码,发现默认是InMemoryRouteDefinitionRepository这个类来存储RouteDefinition信息,我们来看看InMemoryRouteDefinitionRepository的实现,发现是用map来存储RouteDefinition的
public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
private final Map<String, RouteDefinition> routes = synchronizedMap(new LinkedHashMap<String, RouteDefinition>());
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap(r -> {
if (StringUtils.isEmpty(r.getId())) {
return Mono.error(new IllegalArgumentException("id may not be empty"));
}
routes.put(r.getId(), r);
return Mono.empty();
});
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap(id -> {
if (routes.containsKey(id)) {
routes.remove(id);
return Mono.empty();
}
return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId)));
});
}
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(routes.values());
}
}
如果spring.cloud.gateway.discovery.locator.enabled设置为true
@ConfigurationProperties("spring.cloud.gateway.discovery.locator")
public class DiscoveryLocatorProperties {
/**
* Flag that enables DiscoveryClient gateway integration.
*/
private boolean enabled = false;
/**
* The prefix for the routeId, defaults to discoveryClient.getClass().getSimpleName()
* + "_". Service Id will be appended to create the routeId.
*/
private String routeIdPrefix;
/**
* SpEL expression that will evaluate whether to include a service in gateway
* integration or not, defaults to: true.
*/
private String includeExpression = "true";
/**
* SpEL expression that create the uri for each route, defaults to: 'lb://'+serviceId.
*/
private String urlExpression = "'lb://'+serviceId";
/**
* Option to lower case serviceId in predicates and filters, defaults to false. Useful
* with eureka when it automatically uppercases serviceId. so MYSERIVCE, would match
* /myservice/**
*/
private boolean lowerCaseServiceId = false;
private List<PredicateDefinition> predicates = new ArrayList<>();
private List<FilterDefinition> filters = new ArrayList<>();
}
通过GatewayDiscoveryClientAutoConfiguration代码可以看到用到了DiscoveryClientRouteDefinitionLocator
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@AutoConfigureBefore(GatewayAutoConfiguration.class)
@AutoConfigureAfter(CompositeDiscoveryClientAutoConfiguration.class)
@ConditionalOnClass({DispatcherHandler.class})
@EnableConfigurationProperties
public class GatewayDiscoveryClientAutoConfiguration {
public static List<PredicateDefinition> initPredicates() {
ArrayList<PredicateDefinition> definitions = new ArrayList<>();
// TODO: add a predicate that matches the url at /serviceId?
// add a predicate that matches the url at /serviceId/**
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(normalizeRoutePredicateName(PathRoutePredicateFactory.class));
predicate.addArg("pattern", "'/'+serviceId+'/**'");
definitions.add(predicate);
return definitions;
}
public static List<FilterDefinition> initFilters() {
ArrayList<FilterDefinition> definitions = new ArrayList<>();
// add a filter that removes /serviceId by default
FilterDefinition filter = new FilterDefinition();
filter.setName(normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));
String regex = "'/' + serviceId + '/(?<remaining>.*)'";
String replacement = "'/${remaining}'";
filter.addArg("regexp", regex);
filter.addArg("replacement", replacement);
definitions.add(filter);
return definitions;
}
@Bean
public DiscoveryLocatorProperties discoveryLocatorProperties() {
DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();
properties.setPredicates(initPredicates());
properties.setFilters(initFilters());
return properties;
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(value = "spring.cloud.discovery.reactive.enabled", matchIfMissing = true)
public static class ReactiveDiscoveryClientRouteDefinitionLocatorConfiguration {
@Bean
@ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled")
public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(
ReactiveDiscoveryClient discoveryClient,
DiscoveryLocatorProperties properties) {
return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
}
}
@Configuration(proxyBeanMethods = false)
@Deprecated
@ConditionalOnProperty(value = "spring.cloud.discovery.reactive.enabled", havingValue = "false")
public static class BlockingDiscoveryClientRouteDefinitionLocatorConfiguration {
@Bean
@ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled")
public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(
DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
}
}
}
继续看DiscoveryClientRouteDefinitionLocator类,发现会从注册中心获取服务创建路由信息
public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
private static final Log log = LogFactory.getLog(DiscoveryClientRouteDefinitionLocator.class);
private final DiscoveryLocatorProperties properties;
private final String routeIdPrefix;
private final SimpleEvaluationContext evalCtxt;
private Flux<List<ServiceInstance>> serviceInstances;
public DiscoveryClientRouteDefinitionLocator(ReactiveDiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
this(discoveryClient.getClass().getSimpleName(), properties);
serviceInstances = discoveryClient.getServices().flatMap(service -> discoveryClient.getInstances(service).collectList());
}
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
SpelExpressionParser parser = new SpelExpressionParser();
Expression includeExpr = parser.parseExpression(properties.getIncludeExpression());
//private String urlExpression = "'lb://'+serviceId";
Expression urlExpr = parser.parseExpression(properties.getUrlExpression());
Predicate<ServiceInstance> includePredicate;
if (properties.getIncludeExpression() == null || "true".equalsIgnoreCase(properties.getIncludeExpression())) {
includePredicate = instance -> true;
} else {
includePredicate = instance -> {
Boolean include = includeExpr.getValue(evalCtxt, instance, Boolean.class);
if (include == null) {
return false;
}
return include;
};
}
return serviceInstances.filter(instances -> !instances.isEmpty())
.map(instances -> instances.get(0)).filter(includePredicate)
.map(instance -> {
String serviceId = instance.getServiceId();
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(this.routeIdPrefix + serviceId);
String uri = urlExpr.getValue(evalCtxt, instance, String.class);
routeDefinition.setUri(URI.create(uri));
final ServiceInstance instanceForEval = new DelegatingServiceInstance(instance, properties);
for (PredicateDefinition original : this.properties.getPredicates()) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);
predicate.addArg(entry.getKey(), value);
}
routeDefinition.getPredicates().add(predicate);
}
for (FilterDefinition original : this.properties.getFilters()) {
FilterDefinition filter = new FilterDefinition();
filter.setName(original.getName());
for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {
String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry);
filter.addArg(entry.getKey(), value);
}
routeDefinition.getFilters().add(filter);
}
return routeDefinition;
});
}
}
看了源码我们可以自定义动态路由,存储在mysql数据库里,并且可以在web界面进行增删改查,具体可以看我的码云代码,我就不具体写了,懒