Detailed introduction to the deployment and use of Spring Cloud Gateway service gateway

1. Why do you need a service gateway:

1. What is a service gateway:

        In the traditional monolithic architecture, only one service needs to be opened for client calls, but in the microservice architecture, a system is divided into multiple microservices. If there is no gateway, the client can only record the invocation of each microservice locally. Address, when the number of microservices to be called is large, it needs to understand the interface of each service, which is a lot of work. What kind of improvement can be achieved after having a gateway?

        As the only traffic entry of the system, the gateway encapsulates the architecture of the internal system. All requests go through the gateway first, and the gateway routes the requests to the appropriate microservices. Therefore, the advantages of using the gateway are:

  • (1) Simplify the work of the client. After the gateway encapsulates the microservices, the client only needs to interact with the gateway without calling different services;
  • (2) Reduce the coupling between functions. Once the service interface is modified, it is only necessary to modify the routing strategy of the gateway, and it is not necessary to modify each client that calls the function, thereby reducing the coupling between programs
  • (3) Free developers to focus on the implementation of business logic. The gateway implements non-business-related functions such as service routing (grayscale and ABTest), load balancing, access control, flow control fuse and downgrade, and does not need to be considered when implementing each service API.

        But the API gateway also has its shortcomings. In the decentralized architecture of microservices, the gateway becomes a central point or bottleneck point, which adds a high-availability component that we must develop, deploy and maintain. It is for this reason that the gateway design must consider that even if the API gateway is down, it will not affect the invocation and operation of the service. Therefore, it is necessary to have the data caching capability for the response of the gateway, and shield the back-end service by returning cached data or default data. s failure.

        The gateway also has certain requirements on the calling method of the service. The API gateway should preferably support I/O asynchronous and synchronous non-blocking. If the service is called synchronously and blocking, it can be understood that there is no complete decoupling between the microservice modules. , that is, if A relies on the API provided by B, if the service provided by B is unavailable, it will directly affect the unavailability of A, unless the synchronous service call is cached accordingly at the API gateway layer or on the client side. Therefore, in order to completely decouple, it is more recommended to choose an asynchronous method for microservice calls. For the scenario where the API gateway needs to combine multiple fine-grained APIs at the bottom, it is recommended to use the responsive programming model instead of the traditional asynchronous callback method to combine code. The API composition itself may be called in parallel or successively, and it is often difficult to control the callback method.

2. The basic functions of the service gateway:

3. The difference between traffic gateway and service gateway:

        The positions of traffic gateways and service gateways in the overall system architecture are shown in the figure above. Traffic gateways (such as Nignx) provide global policies that are not related to back-end business applications, such as HTTPS certificate offloading, web firewall, global Traffic monitoring, etc. Microservice gateways (such as Spring Cloud Gateway) refer to policies that are tightly coupled with the business and provide a single business domain-level strategy, such as service governance and identity authentication. That is to say, the traffic gateway is responsible for north-south traffic scheduling and security protection, and the microservice gateway is responsible for east-west traffic scheduling and service governance.

Second, the deployment of the service gateway:

1. Comparison and selection of mainstream gateways:

 (1) Kong gateway: Kong has very good performance and is very suitable for traffic gateways, but it is not recommended to use Kong for business gateways for complex systems, mainly due to engineering considerations

(2) Zuul1.x gateway: Zuul 1.0 has rich experience in landing, but it has poor performance and is based on synchronous blocking IO. It is suitable for small and medium architectures, but not suitable for scenarios with high concurrent traffic, because it is easy to cause thread exhaustion, resulting in requests being rejected.

(3) gateway gateway: powerful and rich in functions and good performance, the official benchmark RPS (requests per second) is 1.6 times that of Zuul, and it is very compatible with the SpringCloud ecosystem. Streaming programming + support for asynchrony is enough for development have chosen it.

(4) Zuul 2.x: The performance is similar to that of gateway. It is based on non-blocking and supports long connections, but SpringCloud has no plan to integrate zuul2, and Netflix-related components have announced that they have entered a maintenance period, and the prospect is unknown.

        In summary, the gateway gateway is more suitable for the SpringCloud project, and from the perspective of development trends, it is inevitable that the gateway will replace zuul.

2. Construction of Spring Cloud Gateway gateway:

(1) Declare the dependency version number:

	<properties>
		<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
		<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
		<spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version>
	</properties>

	<!-- 只声明依赖,不引入依赖 -->
	<dependencyManagement>
		<dependencies>
			<!-- 声明springBoot版本 -->
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-dependencies</artifactId>
				<version>${spring-boot.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<!-- 声明springCloud版本 -->
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<!-- 声明 springCloud Alibaba 版本 -->
			<dependency>
				<groupId>com.alibaba.cloud</groupId>
				<artifactId>spring-cloud-alibaba-dependencies</artifactId>
				<version>${spring-cloud-alibaba.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

(2) Add dependencies:

<!-- 引入gateway网关 -->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-gateway</artifactId>
	<exclusions>
        <exclusion>
			<groupId>org.springframework.boot</groupId>
        	<artifactId>spring-boot-starter-web</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Note : Be sure to exclude the spring-boot-starter-web dependency, otherwise the startup will report an error

(3) Configure the project name and port:

server:
  port: 9023
  servlet:
    context-path: /${spring.application.name}
spring:
  application:
    name: gateway

Well, the gateway project is completed, in fact, such a dependency is added. The detailed configuration and function are described below.

3. Description of Spring Cloud Gateway configuration items:

        Before introducing the configuration items of Spring Cloud Gateway, let's understand several core terms of Spring Cloud Gateway:

  • Predicate: Refer to Java8's new feature Predicate, which allows developers to match any content in HTTP requests, such as request headers or request parameters, and finally returns a Boolean value according to the matching result.
  • Route: consists of ID, target URI, set of assertions and set of filters. If the aggregate assertion is true, forward to this route.
  • Filter: The content of requests and responses can be modified before or after the request is returned.

3.1. Routing Route:

        Route is mainly composed of route id, target uri, assertion set and filter set, so let's take a brief look at what these properties do.

(1) id: routing identifier, unique and arbitrary name (the default value is uuid, which is generally not used, and needs to be customized)

(2) uri: The destination address to which the request is finally forwarded

(3) order: routing priority, the smaller the number, the higher the priority

(4) predicates: the assertion array, that is, the judgment condition, if the return value is boolean, the request is forwarded to the service specified by the uri attribute

(5) filters: filter array, in the process of request transmission, make some modifications to the request

3.2. Assert Predicate:

        Predicate comes from the Java8 interface. Predicate takes one input parameter and returns a boolean result. This interface contains several default methods to combine Predicates into other complex logic (eg: AND, OR, NOT).

        Predicate can be used to verify interface request parameters, and to determine whether new and old data have changed and need to be updated. Spring Cloud Gateway has many built-in Predicts. The source code of these Predicts is in the org.springframework.cloud.gateway.handler.predicate package. You can read it if you are interested. Some built-in assertions are as follows:

The above 11 assertions will not be described here. The official documentation is very clear: https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/

Let's take the last weight assertion as an example to introduce how to configure it. The configuration is as follows:

spring:
  cloud:
    gateway:
      # 路由数组:指当请求满足什么样的断言时,转发到哪个服务上
      routes:
        # 路由标识,要求唯一,名称任意
        - id: gateway-provider_1
		  # 请求最终被转发到的目标地址
          uri: http://localhost:9024
          # 设置断言
          predicates:
            # Path Route Predicate Factory 断言,满足 /gateway/provider/** 路径的请求都会被路由到 http://localhost:9024 这个uri中
            - Path=/gateway/provider/**
            # Weight Route Predicate Factory 断言,同一分组按照权重进行分配流量,这里分配了80%
            # 第一个group1是分组名,第二个参数是权重
            - Weight=group1, 8
          # 配置过滤器(局部)
          filters:
            # StripPrefix:去除原始请求路径中的前1级路径,即/gateway
            - StripPrefix=1            
            
        - id: gateway-provider_2
          uri: http://localhost:9025
          # 设置断言
          predicates:
            - Path=/gateway/provider/**
            # Weight Route Predicate Factory,同一分组按照权重进行分配流量,这里分配了20%
            - Weight=group1, 2
		  # 配置过滤器(局部)
          filters:
            # StripPrefix:去除原始请求路径中的前1级路径,即/gateway
            - StripPrefix=1            

        The assertion names in Spring Cloud Gateway are standardized in the format: "xxx + RoutePredicateFactory", such as the weight assertion WeightRoutePredicateFactory, then directly take the preceding "Weight" when configuring.

        If the route forwarding matches two or more, it is forwarded according to the configuration order, and the path is configured above: " Path=/gateway/provider/** ", if no weight is configured, it must be forwarded to " http ://localhost:9024 ”, but since the configuration is configured with weights and the same grouping, the traffic is distributed according to the weight ratio.

3.3, filter filter:

Lifecycle of Gateway filter:

  • PRE : This filter is invoked before the request is routed. We can use this filter to implement authentication, select requested microservices in the cluster, log debug information, and more.
  • POST : This filter is executed after routing to the microservice. Such filters can be used to add standard HTTP headers to responses, collect statistics and metrics, send responses from microservices to clients, and more.

Gateway filters can be divided into two types from the scope of action:

  • GatewayFilter : applied to a single route or a grouped route (need to be configured in the configuration file)
  • GlobalFilter : applied to all routes (no configuration required, takes effect globally)

(1) Local filter GatewayFilter:

        There are many local filters built into Spring Cloud Gateway, as shown below:

         The local filter needs to be configured in the specified route to take effect, and it does not take effect by default. Take the filter "AddResponseHeaderGatewayFilterFactory" as an example, add Header to the original response, the configuration is as follows:

spring:
  cloud:
    gateway:
      routes:
        - id: gateway-provider_1
          uri: http://localhost:9024
          predicates:
            - Path=/gateway/provider/**
          # 配置过滤器(局部)
          filters:
            - AddResponseHeader=X-Response-Foo, Bar
            # StripPrefix:去除原始请求路径中的前1级路径,即/gateway
            - StripPrefix=1   

The browser requests and finds that the key-value pair X-Response-Foo=Bar already exists in the response header, as shown below:

        In the previous example, we also used another local filter, StripPrefixGatewayFilterFactory, which is mainly used to truncate the path of the original request. When we request localhost:9023/gateway/provider/test, the actual request will be forwarded to http ://localhost:9024 and truncated to " http://localhost:9024/provider/test "

Note : The name of the filter only needs to write the prefix, and the filter name must be "xxx + GatewayFilterFactory" (including custom).

For more filter configuration, refer to the official documentation: https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#gatewayfilter-factories

(2) Custom local filter:

        Although the built-in filters can solve many scenarios, it is inevitable that some special requirements need to be customized for a filter. Let's introduce how to customize local filters.

/**
 * 名称必须是xxxGatewayFilterFactory形式
 * todo:模拟授权的验证,具体逻辑根据业务完善
 */
@Component
@Slf4j
public class AuthorizeGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthorizeGatewayFilterFactory.Config> {

    private static final String AUTHORIZE_TOKEN = "token";

    //构造函数,加载Config
    public AuthorizeGatewayFilterFactory() {
        //固定写法
        super(AuthorizeGatewayFilterFactory.Config.class);
        log.info("Loaded GatewayFilterFactory [Authorize]");
    }

    //读取配置文件中的参数 赋值到 配置类中
    @Override
    public List<String> shortcutFieldOrder() {
        //Config.enabled
        return Arrays.asList("enabled");
    }

    @Override
    public GatewayFilter apply(AuthorizeGatewayFilterFactory.Config config) {
        return (exchange, chain) -> {
            //判断是否开启授权验证
            if (!config.isEnabled()) {
                return chain.filter(exchange);
            }

            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();
            //从请求头中获取token
            String token = headers.getFirst(AUTHORIZE_TOKEN);
            if (token == null) {
                //从请求头参数中获取token
                token = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);
            }

            ServerHttpResponse response = exchange.getResponse();
            //如果token为空,直接返回401,未授权
            if (StringUtils.isEmpty(token)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                //处理完成,直接拦截,不再进行下去
                return response.setComplete();
            }
            /**
             * todo chain.filter(exchange) 之前的都是过滤器的前置处理
             *
             * chain.filter().then(
             *  过滤器的后置处理...........
             * )
             */
            //授权正常,继续下一个过滤器链的调用
            return chain.filter(exchange);
        };
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Config {
        // 控制是否开启认证
        private boolean enabled;
    }
}

The local filter needs to be configured in the route to take effect. The configuration is as follows:

spring:
  cloud:
    gateway:
      routes:
        - id: gateway-provider_1
          uri: http://localhost:9024
          predicates:
            - Path=/gateway/provider/**
          ## 配置过滤器(局部)
          filters:
            - AddResponseHeader=X-Response-Foo, Bar
            ## AuthorizeGatewayFilterFactory自定义过滤器配置,值为true需要验证授权,false不需要
            - Authorize=true

At this time, visit directly: http://localhost:9023/gateway/provider/port , without token, return as shown below:

The request parameters bring the token: http://localhost:9023/gateway/provider/port?token=abcdcdecd-ddcdeicd12 , it returns successfully, as shown below:

        The above AuthorizeGatewayFilterFactory only involves the pre-processing of the filter, and the post-processing is done in the then() method in chain.filter().then(). For details, see the TimeGatewayFilterFactory in the project source code, the code does not Paste it again, as shown below:

(3) GlobalFilter global filter:

        Global filters are applied to all routes without developer configuration. Spring Cloud Gateway also has some built-in global filters, as shown below:

        The function of GlobalFilter is actually the same as that of GatewayFilter, but the scope of GlobalFilter is all routing configurations, not bound to the specified routing configuration. Multiple GlobalFilters can specify the execution order through the @Order or getOrder() methods. The smaller the order value, the higher the execution priority.

        Note that since there are two types of filters, pre and post, if the pre type filter has a smaller order value, then it should be at the top of the pre filter chain, and if the post type filter has a smaller order value, then it should be at the top of the pre filter chain. The bottom layer of the post filter chain. The schematic diagram is as follows:

(4) Customize the global filter:

        Of course, in addition to the built-in global filters, custom filters are also required in actual work. Let's introduce how to customize them. We simulate the Access Log function of Nginx to record the relevant information of each request. code show as below:

@Slf4j
@Component
@Order(value = Integer.MIN_VALUE)
public class AccessLogGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //filter的前置处理
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().pathWithinApplication().value();
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        return chain
                //继续调用filter
                .filter(exchange)
                //filter的后置处理
                .then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();
            HttpStatus statusCode = response.getStatusCode();
            log.info("请求路径:{},远程IP地址:{},响应码:{}", path, remoteAddress, statusCode);
        }));
    }
}

Well, the global filter does not need to be configured on the route, it can be injected into the IOC container to take effect globally.

At this point, a request is issued, and the console prints the following information:

Request path: /gateway/provider/port, remote IP address: /0:0:0:0:0:0:0:1:64114, response code: 200 OK

4. Gateway integrates nacos registry to realize service discovery:

        There is no integrated registry in the above demo, and each routing configuration specifies a fixed service uri, as shown below:

What's the harm in doing this?

  • The gateway service needs to know the domain name or IP address of all services. In addition, once the domain name or IP address of the service is modified, the uri in the routing configuration must be modified
  • Load balancing cannot be achieved in the service cluster

        Then we can integrate the registry at this time, so that the gateway can automatically obtain the uri from the registry and achieve load balancing. Here we take the nacos registry as an example to introduce

(1) New dependencies in the pom file:

<!--nacos注册中心-->
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

(2) Add the @EnableDiscoveryClient annotation to the startup class to enable the registry function, as shown below:

 (3) Configure the address of the nacos registry:

nacos:
  namespace: 856a40d7-6548-4494-bdb9-c44491865f63
  url: 120.76.129.106:80
spring:
  cloud:
    nacos:
      discovery:
      	server-addr: ${nacos.url}
        namespace: ${nacos.namespace}
        register-enabled: true

(4) Service routing configuration:

spring:
  cloud:
    gateway:
      routes:
        - id: gateway-provider_1
          ## 使用了lb形式,从注册中心负载均衡的获取uri
          uri: lb://gateway-provider
          ## 配置断言
          predicates:
            - Path=/gateway/provider/**
          filters:
            - AddResponseHeader=X-Response-Foo, Bar

The only difference in the routing configuration is the uri of the route, in the format: lb://service-name, which is a fixed way of writing:

  • lb: fixed format, which refers to obtaining microservices by name from nacos and following the load balancing strategy
  • service-name: The service name of the nacos registry, which is not in the form of an IP address

        Why can load balancing be enabled by specifying lb? As mentioned earlier, the global filter LoadBalancerClientFilter is responsible for routing addressing and load balancing. You can see the following source code:

(5) Enable gateway automatic routing configuration:

        With the continuous development of our system architecture, the number of microservices in the system will definitely increase. It is impossible for us to configure a new routing rule on the gateway every time we add a service. Such maintenance costs are very high; especially in In many cases, we will carry a routing identifier in the request path to facilitate forwarding, and this routing identifier is generally the service name of the service in the registry, so this is where we can enable the automatic routing function of spring cloud gateway, the gateway Automatically create a router for each service based on the service name of the registry, and forward the request path starting with the service name to the corresponding service. The configuration is as follows:

# enabled:默认为false,设置为true表明spring cloud gateway开启服务发现和路由的功能,网关自动根据注册中心的服务名为每个服务创建一个router,将以服务名开头的请求路径转发到对应的服务
spring.cloud.gateway.discovery.locator.enabled = true
# lowerCaseServiceId:启动 locator.enabled=true 自动路由时,路由的路径默认会使用大写ID,若想要使用小写ID,可将lowerCaseServiceId设置为true
spring.cloud.gateway.discovery.locator.lower-case-service-id = true

        It should be noted here that since our gateway project is configured with the server.servlet.context-path property, this will cause the problem of automatic routing failure, so we need to make the following two modifications:

# 重写过滤链,解决项目设置了 server.servlet.context-path 导致 locator.enabled=true 默认路由策略404的问题
spring.cloud.gateway.discovery.locator.filters[0] = PreserveHostHeader
@Configuration
public class GatewayConfig
{
    @Value ("${server.servlet.context-path}")
    private String prefix;

    /**
     * 过滤 server.servlet.context-path 属性配置的项目路径,防止对后续路由策略产生影响,因为 gateway 网关不支持 servlet
     */
    @Bean
    @Order (-1)
    public WebFilter apiPrefixFilter()
    {
        return (exchange, chain) ->
        {
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getRawPath();

            path = path.startsWith(prefix) ? path.replaceFirst(prefix, "") : path;
            ServerHttpRequest newRequest = request.mutate().path(path).build();

            return chain.filter(exchange.mutate().request(newRequest).build());
        };
    }
}

        So far, we have enabled the automatic routing function of spring cloud gateway. The gateway automatically creates a router for each service based on the service name of the registry, and forwards the request path starting with the service name to the corresponding service. Assuming that the service name of our service provider in the nacos registry is "gateway-provider", then we only need to access " http://localhost:9023/gateway/gateway-provider/test " to successfully forward the request past

5. Gateway integrates Apollo to achieve dynamic routing configuration:

        The above examples all write a series of configurations of the gateway into the configuration file of the project. Once the routing policy is changed, the project must be restarted, so the maintenance cost is very high, especially as the service gateway is the central point of the system, once a problem occurs in the restart, it will affect the The surface will be very huge. Therefore, we store the configuration of the gateway in the configuration center, so that the configuration center manages it uniformly. Once the route changes, it only needs to be modified in the configuration center to reduce risks and fail in real time. This section takes the Apollo configuration center as an example to introduce the following implementation of dynamic routing configuration:

(1) Add apollo configuration center dependencies:

<!-- Apollo 统一配置中心 -->
<dependency>
    <groupId>com.ctrip.framework.apollo</groupId>
    <artifactId>apollo-client</artifactId>
    <version>1.7.0</version>
</dependency>

(2) Add the Apollo route change listener refresh class:

import com.ctrip.framework.apollo.enums.PropertyChangeType;
import com.ctrip.framework.apollo.model.ConfigChange;
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;

/**
 * Apollo路由更改监听刷新
 */
@Configuration
public class GatewayPropertRefresher implements ApplicationContextAware, ApplicationEventPublisherAware
{
    private static final Logger logger = LoggerFactory.getLogger(GatewayPropertRefresher.class);

    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;

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }


    /**
     * 监听路由修改
     */
    @ApolloConfigChangeListener(interestedKeyPrefixes = "spring.cloud.gateway.")
    public void onChange(ConfigChangeEvent changeEvent)
    {
        refreshGatewayProperties(changeEvent);
    }

    /**
     * 刷新路由信息
     */
    private void refreshGatewayProperties(ConfigChangeEvent changeEvent)
    {
        logger.info("gateway网关配置 刷新开始!");

        preDestroyGatewayProperties(changeEvent);
        //更新配置
        this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
        //更新路由
        refreshGatewayRouteDefinition();

        logger.info("gateway网关配置 刷新完成!");
    }

    /***
     * 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两个集合清空
     */
    private synchronized void preDestroyGatewayProperties(ConfigChangeEvent changeEvent)
    {
        logger.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.setRoutes(new ArrayList());
        }

        logger.info("Pre Destroy GatewayProperties 操作完成!");
    }


    private void refreshGatewayRouteDefinition()
    {
        logger.info("Refreshing Gateway RouteDefinition 操作开始!");

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

        logger.info("Gateway RouteDefinition refreshed 操作完成!");
    }

    /***
     * 根据changeEvent和定义的pattern匹配key,如果所有对应PropertyChangeType为DELETED则需要清空GatewayProperties里相关集合
     */
    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;
    }
}

(3) Expose the endpoint endpoint:

# 暴露endpoint端点,暴露路由信息,有获取所有路由、刷新路由、查看单个路由、删除路由等方法
management.endpoints.web.exposure.include = *
management.endpoint.health.show-details = always

        So far, we have completed the gateway gateway integration Apollo configuration center to achieve dynamic routing configuration. Once the routing changes, it only needs to be modified in the configuration center to be monitored and invalidated in real time.

If you have integrated Nacos or MySQL for dynamic routing configuration, you can refer to the following two articles:

(1) Integrate Nacos for dynamic routing configuration: https://www.cnblogs.com/jian0110/p/12862569.html

(2) Integrate MySQL for dynamic routing configuration: https://blog.csdn.net/qq_42714869/article/details/92794911

6. Customize the global exception handler:

        Through the previous test, we can see a phenomenon: once the routed microservice is offline or disconnected, Spring Cloud Gateway directly returns an error page, as shown below:

        Obviously, this kind of exception information is not friendly, and the returned exception information must be customized in the front-end and back-end separation architecture. Traditional Spring Boot services use @ControllerAdvice to wrap global exception handling, but because the service is offline, the request does not arrive. Therefore, a layer of global exception handling must be customized in the gateway, so as to interact with the client more friendly.

        Spring Cloud Gateway provides a variety of global processing methods, only one of which is introduced today, and the implementation is quite elegant:

        Create a class GlobalErrorExceptionHandler directly, implement ErrorWebExceptionHandler, and rewrite the handle method. The code is as follows:

/**
 * 用于网关的全局异常处理
 * @Order(-1):优先级一定要比ResponseStatusExceptionHandler低
 */
@Slf4j
@Order(-1)
@Component
@RequiredArgsConstructor
public class GlobalErrorExceptionHandler implements ErrorWebExceptionHandler {

 private final ObjectMapper objectMapper;

 @SuppressWarnings({"rawtypes", "unchecked", "NullableProblems"})
 @Override
 public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
  ServerHttpResponse response = exchange.getResponse();
  if (response.isCommitted()) {
   return Mono.error(ex);
  }

  // JOSN格式返回
  response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
  if (ex instanceof ResponseStatusException) {
   response.setStatusCode(((ResponseStatusException) ex).getStatus());
  }

  return response.writeWith(Mono.fromSupplier(() -> {
   DataBufferFactory bufferFactory = response.bufferFactory();
   try {
    //todo 返回响应结果,根据业务需求,自己定制
    CommonResponse resultMsg = new CommonResponse("500",ex.getMessage(),null);
    return bufferFactory.wrap(objectMapper.writeValueAsBytes(resultMsg));
   }
   catch (JsonProcessingException e) {
    log.error("Error writing response", ex);
    return bufferFactory.wrap(new byte[0]);
   }
  }));
 }
}

        Well, the global exception handling has been customized. Let’s test it. At this time, the JSON data is returned normally (the style of JSON is customized according to the needs of the architecture), as shown in the following figure:

Reference article: Spring Cloud Gateway kills 10 questions?

Guess you like

Origin blog.csdn.net/a745233700/article/details/122917167