JAVA—Spring—SpringCloud—gateway

本文涉及的产品
应用型负载均衡 ALB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
简介: 1. 什么是gateway官网解释: 该项目提供了一个在Spring生态系统之上构建的API网关,包括:Spring 5,Spring Boot 2和Project Reactor。Spring Cloud Gateway旨在提供一种简单而有效的方法来路由到API,并为它们提供跨领域的关注,例如:安全性,监视/指标和弹性。

为什么使用gateway


1.网关作为请求的入口,可以提供限流,权限校验等一系列功能


2.提供路由分发功能


2. 工作原理图

image.png

3. 简单使用


3.1 引入jar包


     

     

           org.springframework.cloud

           spring-cloud-starter-gateway

     

     

     

           com.alibaba.cloud

           spring-cloud-starter-alibaba-nacos-discovery

     

     

           com.alibaba.cloud

           spring-cloud-starter-alibaba-nacos-config

     


     

     

           org.springframework.boot

           spring-boot-starter-actuator

     


3.2  配置文件


spring.application.name=gateway

spring.cloud.nacos.config.namespace=dev

spring.cloud.nacos.config.group=GATEWAY_GROUP

spring.cloud.nacos.config.file-extension=yaml

spring.cloud.nacos.config.server-addr=zy.nacos.com:8848

# gateway默认从注册中心给所有的服务建立一个默认的路由 默认false

spring.cloud.gateway.discovery.locator.enabled=true


3.2.3 动态路由


3.2.1 增加配置bean 用来监听nacos的配置文件修改


package com.zy.more.config;


import java.util.ArrayList;

import java.util.List;

import java.util.Properties;

import java.util.concurrent.Executor;


import javax.annotation.PostConstruct;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.cloud.gateway.event.RefreshRoutesEvent;

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 com.alibaba.fastjson.JSONObject;

import com.alibaba.nacos.api.NacosFactory;

import com.alibaba.nacos.api.config.ConfigService;

import com.alibaba.nacos.api.config.listener.Listener;


import reactor.core.publisher.Mono;


/**

* 动态路由,可以通过获取Bean才做该类,提供增删改查已经发布功能

* @author wunaozai

* @Date 2020-03-16

*/

@Service

public class DynamicRouteConfig implements ApplicationEventPublisherAware {


   private static final Logger log = LoggerFactory.getLogger(DynamicRouteConfig.class);


   @Autowired

   private RouteDefinitionWriter routedefinitionWriter;


   private ApplicationEventPublisher publisher;


   private String dataId = "gateway-router.properties";

   private String group = "DEFAULT_GROUP";

   @Value("${spring.cloud.nacos.config.server-addr}")

   private String serverAddr;

   @Value("${spring.cloud.nacos.config.namespace}")

   private String namespace;


   private static final List ROUTE_LIST = new ArrayList<>();


   @PostConstruct

   public void dynamicRouteByNacosListener() {

       try {

           Properties prop = new Properties();

           prop.put("serverAddr", serverAddr);

           prop.put("namespace", namespace);

           ConfigService config = NacosFactory.createConfigService(prop);

           String content = config.getConfig(dataId, group, 5000);

           publisher(content);

           config.addListener(dataId, group, new Listener() {

               @Override

               public void receiveConfigInfo(String config) {

                   publisher(config);

               }

               @Override

               public Executor getExecutor() {

                   return null;

               }

           });

       } catch (Exception e) {

           e.printStackTrace();

       }

   }


   /**

    * 增加路由

    * @param def

    * @return

    */

   public Boolean addRoute(RouteDefinition def) {

       try {

           routedefinitionWriter.save(Mono.just(def)).subscribe();

           ROUTE_LIST.add(def.getId());

       } catch (Exception e) {

           e.printStackTrace();

       }

       return true;

   }

   /**

    * 删除路由

    * @return

    */

   public Boolean clearRoute() {

       for(String id: ROUTE_LIST) {

           routedefinitionWriter.delete(Mono.just(id)).subscribe();

       }

       ROUTE_LIST.clear();

       return false;

   }

   /**

    * 发布路由

    */

   private void publisher(String config) {

       clearRoute();

       try {

           log.info("重新更新动态路由");

           List gateway = JSONObject.parseArray(config, RouteDefinition.class);

           for(RouteDefinition route: gateway) {

               addRoute(route);

           }

           publisher.publishEvent(new RefreshRoutesEvent(this.routedefinitionWriter));

       } catch (Exception e) {

           e.printStackTrace();

       }


   }


   @Override

   public void setApplicationEventPublisher(ApplicationEventPublisher app) {

       publisher = app;

   }


}


3.2.2 nacos增加json配置文件 gateway-router.properties


[{

   "id": "order",

   "order": 1,

   "predicates": [{

       "args": {

           "pattern": "/order/**"

       },

       "name": "Path"

   }],

   "uri": "lb://order"

},{

   "id": "product",

   "order": 2,

   "predicates": [{

       "args": {

           "pattern": "/product/**"

       },

       "name": "Path"

   }],

   "uri": "lb://product"

},{

   "id": "baidu",

   "order": 3,

   "predicates": [{

       "args": {

           "pattern": "/baidu/**"

       },

       "name": "Path"

   }],

   "uri": "http://www.baidu.com"

}]


这样就能在在不停止gateway服务的情况下,动态的修改gateway路由信息


4. 配置说明


gateway分为三大部分


route: 路由


predicate: 断言


filter: 过滤器


4.1 简介


spring:

 cloud:

   gateway:

     routes: #路由属性

     - id: after_route #id 唯一标识

       uri: https://example.org #当配置到此路由后跳转路径

       predicates: #断言 也就是判断条件 如果符合此条件,就跳转此路径

       - Cookie=mycookie,mycookievalue #条件 predicates默认有11种

       filters: #过滤器

       - AddRequestHeader=X-Request-red, blue #过滤器也有31种 这个过滤器作用是经过的请求都会加上一个请求头


4.2 十一种predicates Factory


4.2.1 After


spring:

 cloud:

   gateway:

     routes:

     - id: after_route

       uri: https://example.org

       predicates:

       - After=2017-01-20T17:42:47.789-07:00[America/Denver]


表示在2017-01-20T17:42:47.789-07:00[America/Denver]这个时间之后的所有请求都会被匹配到这个路由


4.2.2 Before


spring:

 cloud:

   gateway:

     routes:

     - id: after_route

       uri: https://example.org

       predicates:

       - Before=2017-01-20T17:42:47.789-07:00[America/Denver]


表示在2017-01-20T17:42:47.789-07:00[America/Denver]这个时间之前的所有请求都会被匹配到这个路由


4.2.3 Between


spring:

 cloud:

   gateway:

     routes:

     - id: after_route

       uri: https://example.org

       predicates:

       - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-20T17:42:47.789-07:00[America/Denver]


表示在两个时间之间的所有请求都会被匹配到这个路由,第二个时间必须大于第一个时间


上面这三个predicate factory 中的时间都必须是dateTime(java8中的ZoneDateTime)


4.2.4 Cookie


spring:

 cloud:

   gateway:

     routes:

     - id: cookie_route

       uri: https://example.org

       predicates:

       - Cookie=chocolate, ch.p


表示请求中有一个cookie name是chocolate,值可以匹配到正则表达式[ch.p] 才会匹配到这个路由


4.2.5 Header


spring:

 cloud:

   gateway:

     routes:

     - id: cookie_route

       uri: https://example.org

       predicates:

        - Header=X-Request-Id, \d+


表示请求中有一个Headername是X-Request-Id,值可以匹配到正则表达式[\d+] 才会匹配到这个路由


4.2.6 Host


spring:

 cloud:

   gateway:

     routes:

     - id: host_route

       uri: https://example.org

       predicates:

       - Host=**.somehost.org,**.anotherhost.org


表示请求的host主机名如果是.somehost.org,     **.anotherhost.org,则匹配此路由


4.2.7 Method


spring:

 cloud:

   gateway:

     routes:

     - id: method_route

       uri: https://example.org

       predicates:

       - Method=GET,POST


请求如果是HTTP Get/Post请求则匹配此路由


4.2.8 Path


spring:

 cloud:

   gateway:

     routes:

     - id: path_route

       uri: https://example.org

       predicates:

       - Path=/red/{segment},/blue/{segment}


请求路径中能匹配到/red/{segment},/blue/{segment} 则匹配此路由


4.2.9 Query


spring:

 cloud:

   gateway:

     routes:

     - id: query_route

       uri: https://example.org

       predicates:

       - Query=green


请求中包含参数名为green,则会匹配此路由


spring:

 cloud:

   gateway:

     routes:

     - id: query_route

       uri: https://example.org

       predicates:

       - Query=red, gree.


同理,不光可以配置名字,如果两个参数,则第一个参数为参数名,第二个参数为参数值的正则表达式


4.2.10 RemoteAddr


spring:

 cloud:

   gateway:

     routes:

     - id: remoteaddr_route

       uri: https://example.org

       predicates:

       - RemoteAddr=192.168.1.1/24


如果请求中的remoteAddr匹配192.168.1.1/24,则匹配此路由


4.2.11 Weight


spring:

 cloud:

   gateway:

     routes:

     - id: weight_high

       uri: https://weighthigh.org

       predicates:

       - Weight=group1, 8

     - id: weight_low

       uri: https://weightlow.org

       predicates:

       - Weight=group1, 2


权重配置,20%的请求会被发送至weight_low,80%的请求会被发送至weight_heigh


4.2.12 总结


  1. 当一个路由中存在多个predicate时,所有的条件都必须满足才能匹配此路由
  2. 当一个请求满足多个路由的predicate的条件时,只有第一个满足条件的路由会生效


4.3 过滤器


4.3.1 AddRequestHeader


spring:

 cloud:

   gateway:

     routes:

     - id: add_request_header_route

       uri: https://example.org

       filters:

       - AddRequestHeader=X-Request-red, blue


顾名思义,把所有匹配到此路由的请求的请求头都添加一个Header X-Request-red:blue


4.3.2 AddRequestParameter


spring:

 cloud:

   gateway:

     routes:

     - id: add_request_parameter_route

       uri: https://example.org

       filters:

       - AddRequestParameter=red, blue


顾名思义,把所有匹配到此路由的请求中都添加一个参数param red:blue


4.3.3 AddResponseHeader


spring:

 cloud:

   gateway:

     routes:

     - id: add_response_header_route

       uri: https://example.org

       filters:

       - AddResponseHeader=X-Response-Red, Blue


把所有到此路由的请求的Response中增加响应头 X-Response-Red:Blue


4.3.4 DedupeResponseHeader


spring:

 cloud:

   gateway:

     routes:

     - id: dedupe_response_header_route

       uri: https://example.org

       filters:

       - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin


去重,去除经过此路由的请求中的响应头的Access-Control-Allow-Credentials 和 Access-Control-Allow-Origin的重复值


..................................官网有三十多种,官网地址..........................................................


https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.3.RELEASE/reference/html/#the-addrequestheader-gatewayfilter-factory


4.4 自定义过滤器


4.4.1 自定义全局过滤器


全局过滤器不需要在配置文件中配置,因为所有的请求都要经过全局过滤器


一个简单的全局过滤器


package com.zy.more.filter;


import lombok.extern.slf4j.Slf4j;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;

import org.springframework.cloud.gateway.filter.GlobalFilter;

import org.springframework.core.Ordered;

import org.springframework.stereotype.Component;

import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Mono;


/**

* @author: zhangyao

* @create:2020-07-16 14:38

**/

@Component

@Slf4j

public class AuthorGlobalFilter implements GlobalFilter, Ordered {

   @Override

   public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

       log.info("我的自定义全局过滤器启动");

       return chain.filter(exchange);

   }


   @Override

   public int getOrder() {

       return 0;

   }

}


4.4.2 自定义过滤器


定义一个过滤器,并在配置文件中指定某个路由使用


  1. 定义一个实现了GatewayFilter接口的类
  2. 把这个类注入进GatewayFIlterFactory配置中


package com.zy.more.filter;


import lombok.extern.slf4j.Slf4j;

import org.springframework.cloud.gateway.filter.GatewayFilter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;

import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;

import org.springframework.core.Ordered;

import org.springframework.stereotype.Component;

import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Mono;


/**

* @author: zhangyao

* @create:2020-07-16 15:12

**/

@Component

@Slf4j

public class MySingleGatewayFilterFactory extends AbstractGatewayFilterFactory {


   @Override

   public GatewayFilter apply(Object config) {

       return new MyTestGatewayFilter();

   }



   public class MyTestGatewayFilter implements GatewayFilter, Ordered {


       @Override

       public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

           log.info("111111111111111111");

           return chain.filter(exchange);

       }


       @Override

       public int getOrder() {

           return 0;

       }

   }

}


5.源码分析


在分析源码之前,猜测一下gateway的运行原理

image.png

  1. 请求先进入 Gateway Handler mappeing 中解析出对应的路由 Route 和 Predicates
  2. 再进入 Gateway Web Handler 中转发到不同的服务
  3. 在发送到不同的服务之前和之后都有对应的过滤器来对请求进行处理


以上都是猜测,进行源码分析


5.1 入口(初始化自动配置类)


查看gateway core包的Spring.factories文件


# Auto Configure

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

#用来验证项目中是否存在DispatcherServlet(SpringMvc使用,webFlux中不能存在) 是否不存在DispatcherHandler(WebFlux使用)

org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration,\

#核心配置类 配置gateway Route Filter 等相关bean

org.springframework.cloud.gateway.config.GatewayAutoConfiguration,\

#负载均衡配置

org.springframework.cloud.gateway.config.GatewayLoadBalancerClientAutoConfiguration,\

org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration,\

#缓存相关配置

org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration,\

#服务发现相关配置 gateway支持从注册中心查询服务并生成对应路由

org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration


org.springframework.boot.env.EnvironmentPostProcessor=\

org.springframework.cloud.gateway.config.GatewayEnvironmentPostProcessor


5.1.1 GatewayClassPathWarningAutoConfiguration


//

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//


package org.springframework.cloud.gateway.config;


import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.springframework.boot.autoconfigure.AutoConfigureBefore;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;

import org.springframework.context.annotation.Configuration;


@Configuration

@AutoConfigureBefore({GatewayAutoConfiguration.class})

public class GatewayClassPathWarningAutoConfiguration {

   private static final Log log = LogFactory.getLog(GatewayClassPathWarningAutoConfiguration.class);

   private static final String BORDER = "\n\n**********************************************************\n\n";


   public GatewayClassPathWarningAutoConfiguration() {

   }


   @Configuration

   @ConditionalOnMissingClass({"org.springframework.web.reactive.DispatcherHandler"})

   protected static class WebfluxMissingFromClasspathConfiguration {

       public WebfluxMissingFromClasspathConfiguration() {

           GatewayClassPathWarningAutoConfiguration.log.warn("\n\n**********************************************************\n\nSpring Webflux is missing from the classpath, which is required for Spring Cloud Gateway at this time. Please add spring-boot-starter-webflux dependency.\n\n**********************************************************\n\n");

       }

   }


   @Configuration

   @ConditionalOnClass(

       name = {"org.springframework.web.servlet.DispatcherServlet"}

   )

   protected static class SpringMvcFoundOnClasspathConfiguration {

       public SpringMvcFoundOnClasspathConfiguration() {

           GatewayClassPathWarningAutoConfiguration.log.warn("\n\n**********************************************************\n\nSpring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. Please remove spring-boot-starter-web dependency.\n\n**********************************************************\n\n");

       }

   }

}


在项目启动时监测是否有 org.springframework.web.servlet.DispatcherServlet 这个类是SpringMvc使用,不能在Gateway中使用,因为Gateway是基于WebFlux的,同理,项目中必须有 org.springframework.web.reactive.DispatcherHandler


5.1.2 GatewayAutoConfiguration


gateway核心配置类,在此配置类中注入了Gateway相关的路由,过滤器相关bean


5.1.2.1 Route的加载过程


5.1.2.1.1 PropertiesRouteDefinitionLocator


从配置文件中获取路由信息


//

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//


package org.springframework.cloud.gateway.config;


import org.springframework.cloud.gateway.route.RouteDefinition;

import org.springframework.cloud.gateway.route.RouteDefinitionLocator;

import reactor.core.publisher.Flux;


public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {

   private final GatewayProperties properties;


   public PropertiesRouteDefinitionLocator(GatewayProperties properties) {

       this.properties = properties;

   }


   public Flux getRouteDefinitions() {

       //这里调用的GatewayProperties中的getRoutes方法,也就是读取了配置文件中的路由信息

       return Flux.fromIterable(this.properties.getRoutes());

   }

}


5.1.2.1.2 RouteDefinitionRouteLocator


路由定义类(RouteDefinition) 转换为路由类(Route)


   private Route convertToRoute(RouteDefinition routeDefinition) {

       AsyncPredicate predicate = this.combinePredicates(routeDefinition);

       List gatewayFilters = this.getFilters(routeDefinition);

       return ((AsyncBuilder)Route.async(routeDefinition).asyncPredicate(predicate).replaceFilters(gatewayFilters)).build();

   }


5.1.2.1.3 CompositeRouteDefinitionLocator


这个类是对RouteDefinitionLocator的包装,就是从 PropertiesRouteDefinitionLocator(配置文件中读取) InMemoryRouteDefinitionRepository(内存中读取) CachingRouteDefinitionLocator(缓存中读取)


DiscoveryClientRouteDefinitionLocator(注册中心服务发现中获取路由)


//

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//


package org.springframework.cloud.gateway.route;


import reactor.core.publisher.Flux;


public class CompositeRouteDefinitionLocator implements RouteDefinitionLocator {

   private final Flux delegates;


   public CompositeRouteDefinitionLocator(Flux delegates) {

       this.delegates = delegates;

   }


   public Flux getRouteDefinitions() {

       //调用RouteDefinitionLocator的各个实现类的getRouteDefinitions获取路由定义

       return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions);

   }

}


5.1.2.1.4 CachingRouteLocator


是对RouteLocator的封装,直接从内存中读取路由信息


 public CachingRouteLocator(RouteLocator delegate) {

       this.delegate = delegate;

       this.routes = CacheFlux.lookup(this.cache, "routes", Route.class).onCacheMissResume(() -> {

           return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE);

       });

   }


   public Flux getRoutes() {

       return this.routes;

   }


5.1.2.1.5 InMemoryRouteDefinitionRepository


默认可以从内存中获取路由,或者可以自己实现 RouteDefinitionRepository,从数据库等媒介中读取路由信息


//

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//


package org.springframework.cloud.gateway.route;


import java.util.Collections;

import java.util.LinkedHashMap;

import java.util.Map;

import org.springframework.cloud.gateway.support.NotFoundException;

import reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;


public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {

   private final Map routes = Collections.synchronizedMap(new LinkedHashMap());


   public InMemoryRouteDefinitionRepository() {

   }


   public Mono save(Mono route) {

       return route.flatMap((r) -> {

           this.routes.put(r.getId(), r);

           return Mono.empty();

       });

   }


   public Mono delete(Mono routeId) {

       return routeId.flatMap((id) -> {

           if (this.routes.containsKey(id)) {

               this.routes.remove(id);

               return Mono.empty();

           } else {

               return Mono.defer(() -> {

                   return Mono.error(new NotFoundException("RouteDefinition not found: " + routeId));

               });

           }

       });

   }


   public Flux getRouteDefinitions() {

       return Flux.fromIterable(this.routes.values());

   }

}


5.1.3 GatewayLoadBalancerClientAutoConfiguration


负载均衡过滤器配置


//

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//


package org.springframework.cloud.gateway.config;


import org.springframework.boot.autoconfigure.AutoConfigureAfter;

import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

import org.springframework.boot.context.properties.EnableConfigurationProperties;

import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;

import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;

import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.reactive.DispatcherHandler;


@Configuration

@ConditionalOnClass({LoadBalancerClient.class, RibbonAutoConfiguration.class, DispatcherHandler.class})

@AutoConfigureAfter({RibbonAutoConfiguration.class})

@EnableConfigurationProperties({LoadBalancerProperties.class})

public class GatewayLoadBalancerClientAutoConfiguration {

   public GatewayLoadBalancerClientAutoConfiguration() {

   }


   @Bean

   @ConditionalOnBean({LoadBalancerClient.class})

   @ConditionalOnMissingBean({LoadBalancerClientFilter.class})

   public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client, LoadBalancerProperties properties) {

       return new LoadBalancerClientFilter(client, properties);

   }

}


注入LoadBalancerClientFilter 过滤器,引入RibbonLoadBalanceClient (gateway也默认引入了Ribbon)


点进去看下 LoadBalancerClientFilter 的作用


public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

       URI url = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);

       String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);

       if (url != null && ("lb".equals(url.getScheme()) || "lb".equals(schemePrefix))) {

           ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);

           log.trace("LoadBalancerClientFilter url before: " + url);

           ServiceInstance instance = this.choose(exchange);

           if (instance == null) {

               String msg = "Unable to find instance for " + url.getHost();

               if (this.properties.isUse404()) {

                   throw new LoadBalancerClientFilter.FourOFourNotFoundException(msg);

               } else {

                   throw new NotFoundException(msg);

               }

           } else {

               URI uri = exchange.getRequest().getURI();

               String overrideScheme = instance.isSecure() ? "https" : "http";

               if (schemePrefix != null) {

                   overrideScheme = url.getScheme();

               }


               URI requestUrl = this.loadBalancer.reconstructURI(new LoadBalancerClientFilter.DelegatingServiceInstance(instance, overrideScheme), uri);

               log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);

               exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);

               return chain.filter(exchange);

           }

       } else {

           return chain.filter(exchange);

       }

   }


这个过滤器的作用就是判断路由的uri是不是包含 lb:开头


如果是,就使用ribbon负载均衡去转发请求


5.1.4 GatewayMetricsAutoConfiguration


注入监控相关filter


5.1.5 GatewayRedisAutoConfiguration


缓存注入


5.1.6 GatewayDiscoveryClientAutoConfiguration


//

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//


package org.springframework.cloud.gateway.discovery;


import java.util.ArrayList;

import java.util.List;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;

import org.springframework.boot.autoconfigure.AutoConfigureBefore;

import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

import org.springframework.boot.context.properties.EnableConfigurationProperties;

import org.springframework.cloud.client.discovery.DiscoveryClient;

import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration;

import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;

import org.springframework.cloud.gateway.filter.FilterDefinition;

import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;

import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory;

import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;

import org.springframework.cloud.gateway.support.NameUtils;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.reactive.DispatcherHandler;


@Configuration

@ConditionalOnProperty(

   name = {"spring.cloud.gateway.enabled"},

   matchIfMissing = true

)

@AutoConfigureBefore({GatewayAutoConfiguration.class})

@AutoConfigureAfter({CompositeDiscoveryClientAutoConfiguration.class})

@ConditionalOnClass({DispatcherHandler.class, DiscoveryClient.class})

@EnableConfigurationProperties

public class GatewayDiscoveryClientAutoConfiguration {

   public GatewayDiscoveryClientAutoConfiguration() {

   }


   @Bean

   @ConditionalOnBean({DiscoveryClient.class})

   @ConditionalOnProperty(

       name = {"spring.cloud.gateway.discovery.locator.enabled"}

   )

   public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {

       return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);

   }


   @Bean

   public DiscoveryLocatorProperties discoveryLocatorProperties() {

       DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();

       properties.setPredicates(initPredicates());

       properties.setFilters(initFilters());

       return properties;

   }


   public static List initPredicates() {

       ArrayList definitions = new ArrayList();

       PredicateDefinition predicate = new PredicateDefinition();

       predicate.setName(NameUtils.normalizeRoutePredicateName(PathRoutePredicateFactory.class));

       predicate.addArg("pattern", "'/'+serviceId+'/**'");

       definitions.add(predicate);

       return definitions;

   }


   public static List initFilters() {

       ArrayList definitions = new ArrayList();

       FilterDefinition filter = new FilterDefinition();

       filter.setName(NameUtils.normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));

       String regex = "'/' + serviceId + '/(?.*)'";

       String replacement = "'/${remaining}'";

       filter.addArg("regexp", regex);

       filter.addArg("replacement", replacement);

       definitions.add(filter);

       return definitions;

   }

}


当接入有注册中心时,并在配置文件中开启 spring.cloud.gateway.enabled 后,gateway会从注册中心中查询所有服务,并创建对应的路由,默认断言是 PathRoutePredicateFactory  "pattern", "'/'+serviceId+'/**'"


5.2 请求处理


5.2.1 DispatcherHandler 请求分发


服务启动,首先进入 DispatcherHandler (类似springmvc的DispatcherServlet) 的 initStrategies 方法,初始化所有的HandlerMapping放入上下文


当有请求进入网关时,同样也是进入DispatcherHandler,DispatcherHandler执行handle方法


protected void initStrategies(ApplicationContext context) {

    //获取所有HandlerMapping接口的实现类

       Map mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);

    //转换成List

       ArrayList mappings = new ArrayList(mappingBeans.values());

    //排序(Order接口)

       AnnotationAwareOrderComparator.sort(mappings);

    //将转换后的Mappings转换为一个不能修改,只读的List

       this.handlerMappings = Collections.unmodifiableList(mappings);

    //获取所有HandlerAdapter的实现类

       Map adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);

    //转换为list

       this.handlerAdapters = new ArrayList(adapterBeans.values());

    //排序

       AnnotationAwareOrderComparator.sort(this.handlerAdapters);

    //获取所有HandlerResultHandler的实现类

       Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerResultHandler.class, true, false);

    //转为list

       this.resultHandlers = new ArrayList(beans.values());

    //排序

       AnnotationAwareOrderComparator.sort(this.resultHandlers);

   }


//请求的处理方法

   public Mono handle(ServerWebExchange exchange) {

       //如果初始化的方法里handlerMappings不为空,就执行handlerMapping的getHandler方法

       return this.handlerMappings == null ? this.createNotFoundError() : Flux.fromIterable(this.handlerMappings).concatMap((mapping) -> {

           return mapping.getHandler(exchange);

       }).next().switchIfEmpty(this.createNotFoundError()).flatMap((handler) -> {

           return this.invokeHandler(exchange, handler);

       }).flatMap((result) -> {

           return this.handleResult(exchange, result);

       });

   }


进入handlerMapping实现类AbstractHandlerMappinggetHandler方法


这个方法是用来获取所有的handler的


public Mono getHandler(ServerWebExchange exchange) {

    //函数式编程获取getHandlerInternal

       return this.getHandlerInternal(exchange).map((handler) -> {

           if (this.logger.isDebugEnabled()) {

               this.logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);

           }


           if (CorsUtils.isCorsRequest(exchange.getRequest())) {

               CorsConfiguration configA = this.corsConfigurationSource.getCorsConfiguration(exchange);

               CorsConfiguration configB = this.getCorsConfiguration(handler, exchange);

               CorsConfiguration config = configA != null ? configA.combine(configB) : configB;

               if (!this.getCorsProcessor().process(config, exchange) || CorsUtils.isPreFlightRequest(exchange.getRequest())) {

                   return REQUEST_HANDLED_HANDLER;

               }

           }


           return handler;

       });

   }

//抽象方法 由每个继承类定义具体的实现

protected abstract Mono getHandlerInternal(ServerWebExchange var1);


5.2.2 RoutePredicateHandlerMapping 获取路由


再看 getHandlerInternal 方法


进入AbstractHandlerMapping的实现类 RoutePredicateHandlerMapping


这个类是通过断言来过滤符合条件的路由,并转发到处理类


protected Mono getHandlerInternal(ServerWebExchange exchange) {

   //判断请求的端口是否是健康检查端口

       if (this.managmentPort != null && exchange.getRequest().getURI().getPort() == this.managmentPort) {

           return Mono.empty();

       } else {

           //把RoutePredicateHandlerMapping这个handlerMaping放入exchange上下文中

           exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_HANDLER_MAPPER_ATTR, this.getSimpleName());

           //lookupRoute过滤出符合处理请求的路由

           return this.lookupRoute(exchange).flatMap((r) -> {

               exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);

               if (this.logger.isDebugEnabled()) {

                   this.logger.debug("Mapping [" + this.getExchangeDesc(exchange) + "] to " + r);

               }

  //把符合条件的路由放入exchange上下文中

               exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, r);

               //交给webHandler处理

               return Mono.just(this.webHandler);

           }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {

               exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);

               if (this.logger.isTraceEnabled()) {

                   this.logger.trace("No RouteDefinition found for [" + this.getExchangeDesc(exchange) + "]");

               }


           })));

       }

   }


5.2.3 FilteringWebHandler 过滤


上文中获取到路由之后,就需要对请求进行过滤处理


除了全局过滤器外,还要获取路由中定义的网关过滤器


//构造函数中对全局过滤器进行处理

public FilteringWebHandler(List globalFilters) {

   this.globalFilters = loadFilters(globalFilters);

}


//处理全局过滤器

private static List loadFilters(List filters) {

   //对GlobalFilter进行包装 如果实现了Ordered接口(有排序),就包装成OrderedGatewayFilter 如果没有实现排序接口,就用gatewayFilter包装

   return (List)filters.stream().map((filter) -> {

       FilteringWebHandler.GatewayFilterAdapter gatewayFilter = new FilteringWebHandler.GatewayFilterAdapter(filter);

       if (filter instanceof Ordered) {

           int order = ((Ordered)filter).getOrder();

           return new OrderedGatewayFilter(gatewayFilter, order);

       } else {

           return gatewayFilter;

       }

   }).collect(Collectors.toList());

}


//过滤链处理

public Mono handle(ServerWebExchange exchange) {

   //获取exchange上下文中的route路由信息

   Route route = (Route)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);

   //获取路由中的过滤器

   List gatewayFilters = route.getFilters();

   //全局过滤器

   List combined = new ArrayList(this.globalFilters);

   //全局过滤器加上路由中的过滤器

   combined.addAll(gatewayFilters);

   //排序

   AnnotationAwareOrderComparator.sort(combined);

   if (logger.isDebugEnabled()) {

       logger.debug("Sorted gatewayFilterFactories: " + combined);

   }


   //递归调用此类中的内部类DefaultGatewayFilterChain过滤器链进行过滤处理

   return (new FilteringWebHandler.DefaultGatewayFilterChain(combined)).filter(exchange);

}


递归调用


private static class DefaultGatewayFilterChain implements GatewayFilterChain {

       private final int index;

       private final List filters;


       public DefaultGatewayFilterChain(List filters) {

           this.filters = filters;

           this.index = 0;

       }


       private DefaultGatewayFilterChain(FilteringWebHandler.DefaultGatewayFilterChain parent, int index) {

           this.filters = parent.getFilters();

           this.index = index;

       }


       public List getFilters() {

           return this.filters;

       }


       public Mono filter(ServerWebExchange exchange) {

           return Mono.defer(() -> {

               if (this.index < this.filters.size()) {

                   GatewayFilter filter = (GatewayFilter)this.filters.get(this.index);

                   FilteringWebHandler.DefaultGatewayFilterChain chain = new FilteringWebHandler.DefaultGatewayFilterChain(this, this.index + 1);

                   return filter.filter(exchange, chain);

               } else {

                   return Mono.empty();

               }

           });

       }

   }


5.2.4 转发请求


转发请求由两个全局过滤器实现


NettyRoutingFilterLoadBalancerClientFilter


5.2.4.1 LoadBalancerClientFilter


如果路由的uri是lb开头,就会进入负载均衡从注册中心获取地址


否则进入下一个过滤器


public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

       URI url = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);

       String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);

       if (url != null && ("lb".equals(url.getScheme()) || "lb".equals(schemePrefix))) {

           ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);

           log.trace("LoadBalancerClientFilter url before: " + url);

           ServiceInstance instance = this.choose(exchange);

           if (instance == null) {

               String msg = "Unable to find instance for " + url.getHost();

               if (this.properties.isUse404()) {

                   throw new LoadBalancerClientFilter.FourOFourNotFoundException(msg);

               } else {

                   throw new NotFoundException(msg);

               }

           } else {

               URI uri = exchange.getRequest().getURI();

               String overrideScheme = instance.isSecure() ? "https" : "http";

               if (schemePrefix != null) {

                   overrideScheme = url.getScheme();

               }


               URI requestUrl = this.loadBalancer.reconstructURI(new LoadBalancerClientFilter.DelegatingServiceInstance(instance, overrideScheme), uri);

               log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);

               exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);

               return chain.filter(exchange);

           }

       } else {

           return chain.filter(exchange);

       }

   }


5.2.4.2 NettyRoutingFilter


如果是http开头或者https开头就通过这个过滤器来转发请求


public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

       URI requestUrl = (URI)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);

       String scheme = requestUrl.getScheme();

       if (!ServerWebExchangeUtils.isAlreadyRouted(exchange) && ("http".equals(scheme) || "https".equals(scheme))) {

           ServerWebExchangeUtils.setAlreadyRouted(exchange);

           ServerHttpRequest request = exchange.getRequest();

           HttpMethod method = HttpMethod.valueOf(request.getMethodValue());

           String url = requestUrl.toString();

           HttpHeaders filtered = HttpHeadersFilter.filterRequest((List)this.headersFilters.getIfAvailable(), exchange);

           DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();

           filtered.forEach(httpHeaders::set);

           String transferEncoding = request.getHeaders().getFirst("Transfer-Encoding");

           boolean chunkedTransfer = "chunked".equalsIgnoreCase(transferEncoding);

           boolean preserveHost = (Boolean)exchange.getAttributeOrDefault(ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE, false);

           Flux responseFlux = ((RequestSender)this.httpClient.chunkedTransfer(chunkedTransfer).request(method).uri(url)).send((req, nettyOutbound) -> {

               req.headers(httpHeaders);

               if (preserveHost) {

                   String host = request.getHeaders().getFirst("Host");

                   req.header("Host", host);

               }


               return nettyOutbound.options(SendOptions::flushOnEach).send(request.getBody().map((dataBuffer) -> {

                   return ((NettyDataBuffer)dataBuffer).getNativeBuffer();

               }));

           }).responseConnection((res, connection) -> {

               ServerHttpResponse response = exchange.getResponse();

               HttpHeaders headers = new HttpHeaders();

               res.responseHeaders().forEach((entry) -> {

                   headers.add((String)entry.getKey(), (String)entry.getValue());

               });

               String contentTypeValue = headers.getFirst("Content-Type");

               if (StringUtils.hasLength(contentTypeValue)) {

                   exchange.getAttributes().put("original_response_content_type", contentTypeValue);

               }


               HttpStatus status = HttpStatus.resolve(res.status().code());

               if (status != null) {

                   response.setStatusCode(status);

               } else {

                   if (!(response instanceof AbstractServerHttpResponse)) {

                       throw new IllegalStateException("Unable to set status code on response: " + res.status().code() + ", " + response.getClass());

                   }


                   ((AbstractServerHttpResponse)response).setStatusCodeValue(res.status().code());

               }


               HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter((List)this.headersFilters.getIfAvailable(), headers, exchange, Type.RESPONSE);

               if (!filteredResponseHeaders.containsKey("Transfer-Encoding") && filteredResponseHeaders.containsKey("Content-Length")) {

                   response.getHeaders().remove("Transfer-Encoding");

               }


               exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_HEADER_NAMES, filteredResponseHeaders.keySet());

               response.getHeaders().putAll(filteredResponseHeaders);

               exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR, res);

               exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR, connection);

               return Mono.just(res);

           });

           if (this.properties.getResponseTimeout() != null) {

               responseFlux = responseFlux.timeout(this.properties.getResponseTimeout(), Mono.error(new TimeoutException("Response took longer than timeout: " + this.properties.getResponseTimeout()))).onErrorMap(TimeoutException.class, (th) -> {

                   return new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, (String)null, th);

               });

           }


           return responseFlux.then(chain.filter(exchange));

       } else {

           return chain.filter(exchange);

       }

   }

image.png



相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
71 2
|
19天前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
38 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
14天前
|
JavaScript Java Kotlin
深入 Spring Cloud Gateway 过滤器
Spring Cloud Gateway 是新一代微服务网关框架,支持多种过滤器实现。本文详解了 `GlobalFilter`、`GatewayFilter` 和 `AbstractGatewayFilterFactory` 三种过滤器的实现方式及其应用场景,帮助开发者高效利用这些工具进行网关开发。
|
19天前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
62 5
|
1月前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
基于开源框架Spring AI Alibaba快速构建Java应用
|
22天前
|
负载均衡 Java API
项目中用的网关Gateway及SpringCloud
Spring Cloud Gateway 是一个功能强大、灵活易用的API网关解决方案。通过配置路由、过滤器、熔断器和限流等功能,可以有效地管理和保护微服务。本文详细介绍了Spring Cloud Gateway的基本概念、配置方法和实际应用,希望能帮助开发者更好地理解和使用这一工具。通过合理使用Spring Cloud Gateway,可以显著提升微服务架构的健壮性和可维护性。
28 0
|
2月前
|
JSON Java Maven
实现Java Spring Boot FCM推送教程
本指南介绍了如何在Spring Boot项目中集成Firebase云消息服务(FCM),包括创建项目、添加依赖、配置服务账户密钥、编写推送服务类以及发送消息等步骤,帮助开发者快速实现推送通知功能。
109 2
|
1月前
|
Java 数据库连接 API
Spring 框架的介绍(Java EE 学习笔记02)
Spring是一个由Rod Johnson开发的轻量级Java SE/EE一站式开源框架,旨在解决Java EE应用中的多种问题。它采用非侵入式设计,通过IoC和AOP技术简化了Java应用的开发流程,降低了组件间的耦合度,支持事务管理和多种框架的无缝集成,极大提升了开发效率和代码质量。Spring 5引入了响应式编程等新特性,进一步增强了框架的功能性和灵活性。
45 0
|
1月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
39 0
|
3月前
|
SpringCloudAlibaba API 开发者
新版-SpringCloud+SpringCloud Alibaba
新版-SpringCloud+SpringCloud Alibaba
下一篇
DataWorks