Spring Coudgateway

【Spring Cloud】06-Gateway源码解读

2020-01-14  本文已影响0人  Y了个J

我们一般在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界面进行增删改查,具体可以看我的码云代码,我就不具体写了,懒

实战项目地址

上一篇下一篇

猜你喜欢

热点阅读