SpringCloud基础二(完结)

HTTP客户端Feign

  • 在SpringCloud基础一中,我们利用RestTemplate结合服务注册与发现来发起远程调用的代码如下:

    String url = "http://userservice/user/" + order.getUserId();
    User user = restTemplate.getForObject(url, User.class);
    
    • 以上代码就存在几个问题:
      • 代码可读性差,编程体验不统一
      • 若遇到参数较多,则此时复杂的url就难以维护
    • 为解决以上问题就引入了Fegin来代替RestTemplate,可帮助我们解决以上问题
  • Fegin是一个声明式的http客户端,可优雅的实现http请求的发送

快速入门

本快速入门示例的初始项目fegin-demo的具体搭建过程可详见SpringCloud项目快速搭建部分内容

本快速入门已上传至Gitee的主分支master中,可自行下载

本快速入门省略了服务注册与发现部分的代码搭建,具体搭建过程可详见SpringCloud基础一中的内容。(本示例以Nacos为基础进行演示)

背景说明

注意:背景说明可详见SpringCloud基础一的微服务远程调用中的背景说明

Fegin搭建

  • Step1: 在服务消费者(即order-service模块)的pom文件中引入依赖

    • openfeign依赖

    • loadBalancer依赖

      由于SpringCloud2020之后的版本不在提供默认的负载均衡器,所以需要引入loadBalancer依赖来负载均衡

      若使用的是SpringCloud2020之前的版本则可以不引入该依赖

      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
      
  • Step2: 在服务消费者(即order-service模块)的启动类OrderApplication上添加开启Fegin功能的注解@EnableFeignClients

    package at.guigu;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @Slf4j
    @EnableFeignClients
    @SpringBootApplication
    public class OrderApplication {
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
            log.info("OrderApplication Running");
        }
    }
    
  • Step3: 在服务消费者(即order-service模块)中创建一个与三层架构包同级的clients包,并在该包下创建一个接口UserClient来编写Fegin客户端,代码如下:

    • Step3-1: 给该接口添加一个@FeignClient注解,并给该注解的name或value属性的值设为要使用的服务提供者的服务名

    • Step3-2: 在该接口内部自定义方法来返回自己想要的结果

      package at.guigu.clients;
      
      import at.guigu.po.User;
      import org.springframework.cloud.openfeign.FeignClient;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      
      @FeignClient(name = "userservice")
      public interface UserClient {
          @GetMapping("/user/{id}")
          User findById(@PathVariable("id") Long id);
      }
    

三层架构包代码更改

  • Step1: 修改业务层代码

    • Step1-1:IOrderService接口中添加方法queryOrderById,代码如下:

      注意:接口中的方法默认就是public abstract,此处写出来只是为了演示,实际项目中可详略

      package at.guigu.service;
      
      import at.guigu.po.Order;
      import com.baomidou.mybatisplus.extension.service.IService;
      
      public interface IOrderService extends IService<Order> {
          // 返回包含用户信息的订单信息
          public abstract Order queryOrderById(Long orderId);
      }
      
    • Step1-2:IOrderService接口中添加方法queryOrderById,代码如下:

      package at.guigu.service.impl;
      
      import at.guigu.clients.UserClient;
      import at.guigu.mapper.OrderMapper;
      import at.guigu.po.Order;
      import at.guigu.po.User;
      import at.guigu.service.IOrderService;
      import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
      import lombok.RequiredArgsConstructor;
      import org.springframework.stereotype.Service;
      
      @Service
      @RequiredArgsConstructor
      public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
          // 构造器依赖注入UserClient接口的bean
          private final UserClient userClient;
          /**
           * 获取包含用户信息的订单信息
           * @param orderId
           * @return Order
           */
          @Override
          public Order queryOrderById(Long orderId) {
              // 1 查询订单信息
              Order order = this.getById(orderId);
              // 2 利用Feign发起Http请求来获取用户数据,实现远程调用
              User user = userClient.findById(order.getUserId());
              // 3 封装用户数据存储到订单信息中
              order.setUser(user);
              return order;
          }
      }
      
  • Step2: 表现层OrderController类中的queryOrderById方法代码更改如下

    package at.guigu.controller;
    
    import at.guigu.po.Order;
    import at.guigu.service.IOrderService;
    import lombok.RequiredArgsConstructor;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("order")
    @RequiredArgsConstructor
    public class OrderController {
    
        private final IOrderService orderService;
    
        @GetMapping("{orderId}")
        public Order queryOrderById(@PathVariable("orderId") Long orderId) {
            // 根据id查询订单并返回
            return orderService.queryOrderById(orderId);
        }
    }
    

服务注册与发现

注意:服务注册与发现的这一部分,博主采用了Nacos,其搭建详细步骤可详见Nacos注册中心的搭建步骤,此处不在演示步骤

  • 为了方便后续演示,本项目创建了两个服务提供者(即user-service模块)的服务实例来模拟多实例部署,端口分别为8081和8082
    • 同一服务的多个服务实例的创建过程可详见Eureka服务注册快速入门,其中提到了如何模拟多实例部署的过程

启动演示

  • 以上快速入门步骤全部搭建完成之后,运行服务提供者以及服务消费者的启动类,然后通过PostMan进行测试,结果如下

    在这里插入图片描述

    在这里插入图片描述

  • 博主共进行了四次请求测试,由以上测试结果可知,Feign不仅成功代替了RestTemplate实现了微服务远程调用,而且也实现了负载均衡

  • 完整pom文件代码如下

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>cn.itcast</groupId>
            <artifactId>fegin-demo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </parent>
    
        <artifactId>order-service</artifactId>
    
        <properties>
            <maven.compiler.source>17</maven.compiler.source>
            <maven.compiler.target>17</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
            <!--LoadBalancer依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-loadbalancer</artifactId>
            </dependency>
            <!--nacos客户端服务管理依赖(即Nacos服务发现依赖)-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            </dependency>
            <!--openfeign依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
        </dependencies>
    
    </project>
    

自定义配置

  • Feign可以支持很多的自定义配置,部分常用的自定义配置如下

    类型作用说明
    feign.Logger.Level修改日志级别包含四种不同的级别:NONE(默认)、BASIC、HEADERS、FULL
    feign.codec.Decoder响应结果的解析器http远程调用的结果做解析,例如解析json字符串为java对象
    feign.codec.Encoder请求参数编码将请求参数编码,便于通过http请求发送
    feign. Contract支持的注解格式默认是SpringMVC的注解
    feign. Retryer失败重试机制请求失败的重试机制,默认是没有,不过会使用Ribbon的重试
    • Feign的日志级别共分为四种:
      • NONE:不记录任何日志信息,这是默认值。
      • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
      • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
      • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

自定义日志级别方式一

SpringCloud2020版本之前

  • 全局配置------针对所有服务提供者

    feign:  
      client:
        config: 
          default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
            loggerLevel: FULL #  日志级别 
    
  • 局部配置------针对单个服务提供者

    feign:  
      client:
        config: 
          userservice: # 针对某个微服务的配置
            loggerLevel: FULL #  日志级别 
    

SpringCloud2020版本之后

  • 全局配置------针对所有服务提供者

    spring:
      cloud:
        openfeign:
          client:
            config:
              default:
                logger-level: FULL
    
  • 局部配置------针对单个服务提供者

    spring:
      cloud:
        openfeign:
          client:
            config:
              userservice:
                logger-level: FULL
    

注意:不论是SpringCloud哪个版本,在设置完Fegin的日志级别之后,必须通过logging.level来设置指定feign包的日志级别,否则不会生效,代码如下

logging:
  level:
    # 配置指定包及其子包下的所有类的日志级别为debug---会输出大于等于该级别的日志信息
    at.guigu.clients: debug
  • 由于在创建该项目时,就已经通过logging.level设置at.guigu包下的所有包及其子包下的日志级别为debug,而clients包在at.guigu包下,所以博主在yml配置文件中并未在通过logging.level设置指定feign包的日志级别

  • logging.level的日志级别共分为七种,从低到高依次为:Trace<Debug<Info <Warn<Error<Fatal<OFF

    • 设置日志级别为degug时,会输出大于等于该级别的日志信息

自定义日志级别方式二

方式一采用的是配置文件的形式,方式二采用配置类的形式

  • Step1: 在服务消费者(即order-service模块)中创建一个与三层架构包同级的config包,并在该包下创建一个类DefaultFeignConfiguration,代码如下:

    package at.guigu.config;
    
    import feign.Logger;
    import org.springframework.context.annotation.Bean;
    
    public class DefaultFeignConfiguration  {
        @Bean
        public Logger.Level feignLogLevel(){
            return Logger.Level.BASIC; // 日志级别为BASIC
        }
    }
    
  • Step2: 使该配置类生效

    • 全局配置方式 :为服务消费者(即order-service模块)的启动类中的@EnableFeignClients注解添加defaultConfiguration属性,且属性值为Feign日志级别配置类的类类对象,代码如下:

      package at.guigu;
      
      import at.guigu.config.DefaultFeignConfiguration;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.cloud.openfeign.EnableFeignClients;
      
      @Slf4j
      @EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
      @SpringBootApplication
      public class OrderApplication {
          public static void main(String[] args) {
              SpringApplication.run(OrderApplication.class, args);
              log.info("OrderApplication Running");
          }
      }
      
    • 局部配置方式(以user-service模块为例) :在clients包下找到对应的服务消费者的Feign客户端接口,然后为该接口中的@FeignClient注解添加configuration属性,且属性值为Feign日志级别配置类的类类对象,代码如下:

      package at.guigu.clients;
      
      import at.guigu.config.DefaultFeignConfiguration;
      import at.guigu.po.User;
      import org.springframework.cloud.openfeign.FeignClient;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      
      @FeignClient(name = "userservice", configuration = DefaultFeignConfiguration.class)
      public interface UserClient {
          @GetMapping("/user/{id}")
          User findById(@PathVariable("id") Long id);
      }
      
  • 注意:不论是全局配置方式还是局部配置方式,最后都要通过logging.level来设置指定feign包的日志级别,否则不会生效

    • 通过logging.level来设置指定feign包的日志级别,可详见自定义日志级别的方式一

Feign使用优化

  • Feign底层发起http请求,主要依赖于其它的框架,其底层客户端实现包括:
    • URLConnection:默认实现,不支持连接池
    • Apache HttpClient :支持连接池
    • OKHttp:支持连接池
    • 由其底层客户端实现可知,提高Feign的性能主要手段就是使用 连接池 代替默认的URLConnection
    • 本小节将对HttpClient以及OKHttp都进行演示,实际项目中选择其中一个即可

Apache HttpClient

博主使用的SpringCloud版本为2022,对应的OpenFeign版本为4.x

Spring Cloud2022版本之前或OpenFeign4.x版本之前

  • Step1: 在服务消费者(即order-service模块)的pom文件中引入Apache的HttpClient依赖

    <!--httpClient的依赖 -->
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-httpclient</artifactId>
    </dependency>
    
  • Step2: 在服务消费者(即order-service模块)的application.yml配置文件中配置连接池

    • SpringCloud2020版本之前

      feign:
        httpclient:
          enabled: true # 开启feign对HttpClient的支持
          max-connections: 200 # 最大的连接数
          max-connections-per-route: 50 # 每个路径的最大连接数
      
    • SpringCloud2020版本之后

      spring:
        cloud:
          openfeign:
            httpclient:
              enabled: true
              max-connections: 200
              max-connections-per-route: 50
      
  • Step3: 打断点测试是否生效

    • Step3-1:FeignClientFactoryBean类中的loadBalance方法中打断点

      在这里插入图片描述

    • Step3-2: DEBUG运行服务消费者的启动类进行测试,前端发送请求后会自动跳转到该断点处,然后查看delegate属性值来判断是否设置成功

      注意:测试时要保证服务提供者的启动类处于运行中

      在这里插入图片描述

Spring Cloud2022版本之后或OpenFeign4.x版本之后

注意:

​ 从Spring Cloud OpenFeign 4开始,Feign Apache HttpClient 4不再被支持,而使用Apache HttpClient 5代替。

  • Step1: 在服务消费者(即order-service模块)的pom文件中引入Apache的HttpClient依赖

    <!--httpClient5的依赖-->
    <dependency>
        <groupId>org.apache.httpcomponents.client5</groupId>
        <artifactId>httpclient5</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!--feign-hc5依赖-->
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-hc5</artifactId>
        <!--若为SpringBoot2.x版本,则该依赖版本改为11.x-->
        <version>13.5</version>
    </dependency>
    
  • Step2: 在服务消费者(即order-service模块)的application.yml配置文件中配置连接池

    spring:
      cloud:
        openfeign:
          httpclient:
            hc5:
              enabled: true
    
  • Step3: 打断点测试是否生效

    • Step3-1: 找到Client类,然后找到该类中的execute接口,单击该接口左侧的符号,然后找到对应的实现类FeignBlockingLoadBalancerClient,进入到该实现类中

      在这里插入图片描述

    • Step3-2:FeignBlockingLoadBalancerClient类的execute方法内部打上断点,然后DEBUG运行服务消费者的启动类进行测试,前端发送请求后会自动跳转到该断点处,然后查看delegate属性值来判断是否设置成功

      注意:测试时要保证服务提供者的启动类处于运行中

      在这里插入图片描述

OKHttp

  • Step1: 在服务消费者(即order-service模块)的pom文件中引入Apache的HttpClient依赖

    <!--OK http 的依赖 -->
    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-okhttp</artifactId>
    </dependency>
    
  • Step2: 在服务消费者(即order-service模块)的application.yml配置文件中配置连接池

    • SpringCloud2020版本之前

      feign:
        okhttp:
          enabled: true
      
    • SpringCloud2020版本之后

      spring:
        cloud:
          openfeign:
            okhttp:
              enabled: true
      
  • 打断点测试是否生效

    SpringCloud2020版本之前 :断点测试可详见Apache HttpClient中 Spring Cloud2022版本之前或OpenFeign4.x版本之前 的断点测试步骤

    SpringCloud2020版本之后的断点测试步骤如下:

    • Step3-1: 找到Client类,然后找到该类中的execute接口,单击该接口左侧的符号,然后找到对应的实现类FeignBlockingLoadBalancerClient,进入到该实现类中

      在这里插入图片描述

    • Step3-2:FeignBlockingLoadBalancerClient类的execute方法内部打上断点,然后DEBUG运行服务消费者的启动类进行测试,前端发送请求后会自动跳转到该断点处,然后查看delegate属性值来判断是否设置成功

      注意:测试时要保证服务提供者的启动类处于运行中

      在这里插入图片描述

Feign最佳实践

最佳实践代码示例已上传至Gitee的分支feign-practice中,可自行下载

思路分析

服务消费者(即order-service模块)中关于服务提供者(即user-service模块)的Feign客户端(即UserClient接口)的代码如下

在这里插入图片描述

服务提供者(即user-service模块)中对应的UserController类的代码如下

在这里插入图片描述

分析服务消费者(即order-service模块)中的UserClient接口和服务提供者(即user-service模块)中的UserController类的代码可知它们的代码非常相似,关系图如下

在这里插入图片描述

  • 因此我们可以对其进行实践优化,共有两种方式:
    • 继承方式:将一样的代码封装到接口中,然后通过继承来共享
    • 抽取方式:将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用

继承方式

在这里插入图片描述

  • 原理:给服务消费者的FeignClient和服务提供者的controller类定义统一的API接口作为标准,然后让eignClient和controller类继承该接口

    • 解释:给服务消费者(即order-service模块)中关于服务提供者(即user-service模块)的Feign客户端(即UserClient接口)以及服务提供者(即user-service模块)中对应的controller方法定义一个统一的父接口,然后服务提供者中的Feign客户端和服务消费者中的Controller都继承该接口
  • 优缺点

    • 优点
      • 简单且实现了代码共享
    • 缺点
      • 服务提供方与服务消费方紧耦合
      • 参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表、注解
  • Step1:

抽取方式

进一步分析:

在服务消费者(即order-service模块)中远程调用服务提供者(即user-service模块)中的相关controller类时,会在服务消费者(即order-service模块)内部创建一个Feign客户端来实现远程调用;假设现在有多个服务消费者来远程调用服务提供者(即user-service模块)中的相关controller类,就需要创建多个Feign客户端来实现远程调用,这样就会导致冗余度较高,更不用说在实际项目中了

在这里插入图片描述

因此我们可以将Feign客户端抽取成一个独立模块,并把Feign客户端中的方法所返回的实体类以及Feign的默认配置均抽取到这个独立模块中,供所有服务消费者使用。这样就解决了冗余度过高的问题

  • 原理:将Feign的Client抽取为独立模块,并且把接口有关的POJO、Feign的默认配置都放到这个模块中,提供给所有消费者使用

    • 解释:将UserClient、User、Feign的默认配置都抽取到一个feign-api包中,所有微服务引用该依赖包,即可直接使用

    在这里插入图片描述

  • Step1: 在当前聚合工程(即父工程)fegin-demo下创建一个新的module,命名为feign-api

    注意:该步骤可详见SpringCloud项目搭建快速入门

  • Step2: 在该feign-api模块的pom文件中引入feign的起步依赖(即feign的starter依赖)

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  • Step3: 在feign-api模块的java包下创建at.guigu包,并在该包下创建feign包

  • Step4: 将服务消费者(即order-service模块)中的feign包、config包、po包以及包中的对应类均剪切到feign包下

    在这里插入图片描述

    • 剪切完成后,包结构如下图所示

      在这里插入图片描述

  • Step5: 由于Feign客户端对应的接口剪切到feign-api模块下后,该接口(即UserClient)中使用到的User类的包就需要重新导入

    原因:原来的User类在at.guigu.po包下,现在剪切过来的User类在at.guigu.feign包下

    在这里插入图片描述

在服务消费者(即order-service模块)中的相关操作步骤

  • Step1: 在服务消费者(即order-service模块)的pom文件中引入feign-api模块的依赖

    <!--feign-api模块的依赖-->
    <dependency>
        <groupId>cn.itcast</groupId>
        <artifactId>feign-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
  • Step2: 由于User类现在不在服务消费者(即order-service模块)的po包下,而是在feign-api模块下,所以需要在使用到User类的类中导入feign-api模块的相关类

    在这里插入图片描述

  • Step3: 由于在服务消费者(即order-service模块)的业务层的相关实现类OrderServiceImpl中调用了Feign客户端(即UserClient接口),并且也使用了User类,所以需要在该类中导入feign-api模块的相关类

    在这里插入图片描述

  • Step4: 由于在服务消费者(即order-service模块)的启动类进行了Feign的全局配置,有因为Feign的配置类已剪切到feign-api模块下,所以需要在启动类中导入feign-api模块的相关配置类

    在这里插入图片描述

  • Step5: 重新启动服务消费者(即order-service模块)的启动类进行测试

    在这里插入图片描述

抽取方式相关问题解决

服务消费者(即order-service模块)的启动类被@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)注解修饰,由于该启动类在at.guigu包下,并且服务消费者(即order-service模块)的pom文件中引入了feign-api模块的依赖。

所以该注解能够扫描到本模块和feign-spi模块在at.guigu包下的Feign客户端,从而可以在服务消费者(即order-service模块)的业务层相关实现类中成功依赖注入对应Feign客户端(即UserClient接口)的Bean

由于feign-spi模块下的Feign客户端(即UserClient接口)在at.guigu.feign包下,所以能够扫描到

  • 假设feign-spi模块下的Feign客户端不在at.guigu包下,而是在其它包下(比如com.heima.feign包),则运行服务消费者的启动类后会报错:Field userClient in at.guigu.service.OrderService required a bean of type 'com.heima.feign.clients.UserClient' that could not be found,即找不到com.heima.feign.clients包下的bean(即包扫描问题),解决方法有两种

  • 方法一:指定FeignClient所在包 在服务消费者的启动类中利用@EnableFeignClients注解中的basePackages属性指定Feign应该扫描的包(即feign-api模块的Feign客户端所在包)

    package at.guigu;
    
    import at.guigu.feign.clients.UserClient;
    import at.guigu.feign.config.DefaultFeignConfiguration;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @Slf4j
    // 方式一:指定FeignClient所在包
    @EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class, basePackages = "at.guigu.feign")
    @SpringBootApplication
    public class OrderApplication {
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
            log.info("OrderApplication Running");
        }
    }
    
  • 方法二:指定FeignClient字节码 在服务消费者的启动类中利用@EnableFeignClients注解中的clients属性指定要加载的Feign客户端(即Client接口)

    package at.guigu;
    
    import at.guigu.feign.clients.UserClient;
    import at.guigu.feign.config.DefaultFeignConfiguration;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @Slf4j
    // 方式二:指定FeignClient字节码
    @EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class, clients = {UserClient.class})
    @SpringBootApplication
    public class OrderApplication {
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
            log.info("OrderApplication Running");
        }
    }
    

网关路由

注意:本项目案例已上传至Gitee的分支gateway中,可自行下载

认识网关

  • 网关就是网络的关口 ,是服务的守门神,是所有微服务的统一入口

    • 数据在网络间传输,从一个网络传输到另一网络时就需要经过网关来做数据的路由和转发以及数据安全的校验
  • 更通俗的来讲,网关就像是以前园区传达室的大爷。

    • 外面的人要想进入园区,必须经过大爷的认可,如果你是不怀好意的人,肯定被直接拦截。外面的人要传话或送信,要找大爷。大爷帮你带给目标人。

      在这里插入图片描述

    • 微服务网关就起到同样的作用。前端请求不能直接访问微服务,而是要请求网关:

      • 网关可以做安全控制,也就是登录身份认证和权限校验,校验通过才放行

      • 通过认证后,网关再根据请求判断应该访问哪个微服务,将请求转发过去

        在这里插入图片描述

  • 网关原理

    • 前端请求到后端后首先会到达网关,网关首先会对用户请求进行身份认证和权限校验,两者都通过后,网关会将其请求放行
    • 然后网关会通过服务路由和负载均衡来为其分配一个微服务,并将请求转发到指定微服务
    • 在此同时,若前端请求过多时,则网关会根据下流的微服务所能接受的请求速度来进行放行请求,以此来避免服务压力过大导致系统崩溃
  • 网关作用

    • 权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。
    • 路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。
    • 限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。
  • 在SpringCloud当中,提供了两种网关实现方案:

    • Netflix Zuul:是基于Servlet实现,属于阻塞式编程,目前已经淘汰

    • SpringCloudGateway:是基于Spring的WebFlux技术,完全支持(或属于)响应式编程,吞吐能力更强,属于非阻塞式编程。具体可详见官方网站

快速入门

  • Step1: 在当前聚合工程(即父工程)fegin-demo下创建一个新的module,命名为gateway

    注意:该步骤可详见SpringCloud项目搭建快速入门

  • Step2: 在该gateway模块的pom文件中引入相关依赖

    • Nacos服务注册与发现的依赖:spring-cloud-starter-alibaba-nacos-discovery

    • 负载均衡依赖:spring-cloud-starter-loadbalancer

      Nacos 自2021版本开始已经没有自带ribbon的负载均衡整合,所以就需要引入另一个支持的jar包loadbalancer来实现对@LoadBalanced注解以及LoadBalancer负载均衡的支持

    • 网关依赖:spring-cloud-starter-gateway

    • 初始完整pom文件代码如下

      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
          <parent>
              <groupId>cn.itcast</groupId>
              <artifactId>fegin-demo</artifactId>
              <version>1.0-SNAPSHOT</version>
          </parent>
      
          <artifactId>gateway</artifactId>
      
          <properties>
              <maven.compiler.source>17</maven.compiler.source>
              <maven.compiler.target>17</maven.compiler.target>
              <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
          </properties>
      
          <dependencies>
              <!--LoadBalancer依赖-->
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
              </dependency>
              <!--nacos客户端服务管理依赖(即Nacos服务注册与发现依赖)-->
              <dependency>
                  <groupId>com.alibaba.cloud</groupId>
                  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
              </dependency>
              <!--gateway网关依赖-->
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-gateway</artifactId>
              </dependency>
          </dependencies>
      </project>
      
  • Step3: 在该gateway模块的java目录下创建at.guigu.gateway包,并在该包下创建gateway网关服务的启动类,代码如下

    package at.guigu.gateway;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    @Slf4j
    @SpringBootApplication
    public class GatewayApplication {
        public static void main(String[] args) {
            SpringApplication.run(GatewayApplication.class, args);
            log.info("GatewayApplication Running");
        }
    }
    
  • Step4: 右键源代码配置文件目录(即资源文件resources)→NewFile,创建配置文件application.yml,代码如下:

    • 配置Nacos服务注册与发现的地址

    • 配置路由,主要包括

      • 路由id:路由的唯一标识

      • 路由目标(uri):路由的目标地址,http代表固定地址,lb表示根据服务名负载均衡

      • 路由断言(predicates):判断路由的规则,

      • 路由过滤器(filters):对请求或响应做处理

    server:
      port: 10010
    spring:
      application:
        name: gateway
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 #Nacos地址
            enabled: true
        gateway:
          routes:
            - id: user-service # 自定义路由的唯一标识(此处自定义为模块名)
              uri: lb://userservice #路由的目标地址
              predicates: # 路由断言,判断请求是否符合指定规则
                - Path=/user/** # 路径断言,判断路径是否以/user开头;若是,则符合制定规则
            - id: order-service
              uri: lb://orderservice
              predicates:
                - Path=/order/**
      main:
        web-application-type: reactive
    logging:
      level:
        # 配置指定包及其子包下的所有类的日志级别为debug---会输出大于等于该级别的日志信息
        at.guigu: DEBUG
      # 配置日志输出的时间戳格式
      pattern:
        dateformat: MM-dd HH:mm:ss:SSS
    
  • Step5: 运行该gateway模块的启动类GatewayApplication,然后利用PostMan测试gateway服务是否能够正常运行

    在这里插入图片描述

    在这里插入图片描述

可能出现的问题

  • 运行gateway网关服务的启动类若报如下错误

    在这里插入图片描述

    原因:SpringCloudGateway属于非阻塞式响应编程;而SpringBoot默认是基于Servlet实现的,属于阻塞式编程

    • 方法一: 在application.yml配置文件中设置spring.main.web-application-type=reactive使SpringBoot在启动时初始化一个响应式的Web应用环境,而不是默认的Servlet环境

      server:
        port: 10010
      spring:
        application:
          name: gateway
        cloud:
          nacos:
            discovery:
              server-addr: localhost:8848 #Nacos地址
              enabled: true
          gateway:
            routes:
              - id: user-service # 自定义路由的唯一标识(此处自定义为模块名)
                uri: lb://userservice #路由的目标地址
                predicates: # 路由断言,判断请求是否符合指定规则
                  - Path=/user/** # 路径断言,判断路径是否以/user开头;若是,则符合制定规则
              - id: order-service
                uri: lb://orderservice
                predicates:
                  - Path=/order/**
        main:
          web-application-type: reactive
      logging:
        level:
          # 配置指定包及其子包下的所有类的日志级别为debug---会输出大于等于该级别的日志信息
          # 由于clients包在at.guigu包下,所以并未单独在logging.level下设置feigen包的日志级别
          at.guigu: DEBUG
        # 配置日志输出的时间戳格式
        pattern:
          dateformat: MM-dd HH:mm:ss:SSS
      
    • 方法二: 排除spring-boot-starter-web dependency依赖

      注意:由于该依赖是在聚合工程(即父工程)的pom文件中,且该聚合工程下的子模块user-service、order-service模块均需要使用该依赖,所以此处暂时无法排除该依赖,只能使用第一种方法

  • 运行gateway网关服务的启动类若报如下错误

    在这里插入图片描述

    原因:未配置数据源相关的url

    注意:gateway网关服务是不需要配置数据源的,但是 Spring Boot 自动配置机制 默认需要一个数据源配置,所以解决方式如下:

    • 方法:在gateway服务的启动类GatewayApplication中,给@SpringBootApplication注解添加exclude属性且属性值为DataSourceAutoConfiguration.class,以此来排除数据源自动配置

      package at.guigu.gateway;
      
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
      
      @Slf4j
      @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
      public class GatewayApplication {
          public static void main(String[] args) {
              SpringApplication.run(GatewayApplication.class, args);
              log.info("GatewayApplication Running");
          }
      }
      

网关路由流程图

在这里插入图片描述

  • 网关路由流程图解释

    • 前端发送请求http://127.0.0.1:10010/user/1后会到达Gateway网关服务

      原因:Gateway服务的网关端口为10010,前端发送的请求的端口就是10010,所以前端请求一定会到Gateway网关服务中去

    • Gateway网关服务会根据路由断言来判断当前路径对应的微服务名,然后Gateway网关服务会从Nacos注册中心拉取对应服务的列表,若拉取下来的服务列表中只有一个则会直接发送到该服务中去

      • 由于前端请求的路径为/user/1对应的路径断言为/user/**,所以当前请求会被转发到userservice服务中去
    • 若对应的微服务有多个,则会通过负载均衡策略选择其中一个,然后将当前请求转发到对应的userservice服务中去

断言工厂(Route Predicate Factory)

  • predicates:即路由断言,判断当前请求是否符合要求,若符合则转发到路由目标地址。代码示例如下:

    spring:
      cloud:
        gateway:
          routes:
            - id: user-service # 自定义路由的唯一标识(此处自定义为模块名)
              uri: lb://userservice #路由的目标地址
              predicates: # 路由断言,判断请求是否符合指定规则
                - Path=/user/** # 路径断言,判断路径是否以/user开头;若是,则符合制定规则
            - id: order-service
              uri: lb://orderservice
              predicates:
                - Path=/order/**
    

    注意:在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件

    比如:Path=/user/**是按照路径匹配,该规则是由

    org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来

    处理的

  • 在SpringCloudGateway中的断言工厂有十几个:

    注意:只需掌握Path这种路由工程就可以了

    名称说明示例
    After是某个时间点后的请求- After=2037-01-20T17:42:47.789-07:00[America/Denver]
    Before是某个时间点之前的请求- Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
    Between是某两个时间点之前的请求- Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
    Cookie请求必须包含某些cookie- Cookie=chocolate, ch.p
    Header请求必须包含某些header- Header=X-Request-Id, \d+
    Host请求必须是访问某个host(域名)- Host=**.somehost.org,**.anotherhost.org
    Method请求方式必须是指定方式- Method=GET,POST
    Path请求路径必须符合指定规则- Path=/red/{segment},/blue/**
    Query请求参数必须包含指定参数- Query=name, Jack或者- Query=name
    RemoteAddr请求者的ip必须是指定范围- RemoteAddr=192.168.1.1/24
    Weight权重处理
  • 以上断言工厂的使用可详见官网示例,仿照即可,此处以After为例

    spring:
      cloud:
        gateway:
          routes:
            - id: user-service
              uri: lb://userservice
              predicates:
                - Path=/user/**
                - After=2035-01-27-T15:14:47.433+08:00[Asia/Shanghai]
    
    • 解释:若前端发来的请求路径是/user/**且发送的请求时间在指定时区时间之后,此时才满足路由规则,Gateway网关服务才会将其发送到指定的服务中去;若不满足路由规则,则前端报错404

路由过滤器GatewayFilter

  • GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理,如下图所示

    在这里插入图片描述

  • Spring提供了31种不同的路由过滤器工厂,具体可详见官网。此处只进行部分示例

    名称说明
    AddRequestHeader给当前请求添加一个请求头
    RemoveRequestHeader移除请求中的一个请求头
    AddResponseHeader给响应结果中添加一个响应头
    RemoveResponseHeader从响应结果中移除有一个响应头
    RequestRateLimiter限制请求的流量
  • 过滤器作用:对路由的请求或响应进行加工处理,比如添加请求头

  • 配置在某个路由下的过滤器只对当前路由的请求生效;若想要对所有路由均生效,则需要使用defaultFilters

局部过滤器

此处以请求头过滤器AddRequestHeader GatewayFilter Factory 来演示

需求:给所有进入userservice服务的请求添加一个请求头:Truth=nihao

  • Step1: gateway网关服务的配置文件application.yml代码如下:

    • 在自定义的路由user-service中配置请求头过滤器
    server:
      port: 10010
    spring:
      application:
        name: gateway
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 #Nacos地址
            enabled: true
        gateway:
          routes:
            - id: user-service # 自定义路由的唯一标识(此处自定义为模块名)
              uri: lb://userservice #路由的目标地址
              predicates: # 路由断言,判断请求是否符合指定规则
                - Path=/user/** # 路径断言,判断路径是否以/user开头;若是,则符合制定规则
              filters: # 配置指定路由的过滤器
                # 配置请求头过滤器---设置请求头Truth=nihao
                - AddRequestHeader=Truth, nihao
            - id: order-service
              uri: lb://orderservice
              predicates:
                - Path=/order/**
      main:
        web-application-type: reactive
    logging:
      level:
        # 配置指定包及其子包下的所有类的日志级别为debug---会输出大于等于该级别的日志信息
        at.guigu: DEBUG
      # 配置日志输出的时间戳格式
      pattern:
        dateformat: MM-dd HH:mm:ss:SSS
    
  • Step2: 更改服务提供者(即user-service模块)的表现层UserController类中的代码,以便于测试请求头是否添加成功,UserController类代码如下

    • Step2-1:queryById方法添加一个获取请求头信息的参数truth
    • Step2-2: 给该参数添加@RequestHeader注解,并在该注解中利用value指明要获取到的请求头的名称,同时将该注解中的required设为false,以此避免未配置请求头过滤器导致的获取请求头信息失败
    package at.guigu.controller;
    
    import at.guigu.po.User;
    import at.guigu.service.IUserService;
    import lombok.RequiredArgsConstructor;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    @RequiredArgsConstructor
    @RequestMapping("/user")
    public class UserController {
        private final IUserService userService;
    
        @GetMapping("/{id}")
        public User queryById(@PathVariable("id") Long id,
                              @RequestHeader(value = "Truth", required = false) String truth) {
            System.out.println("userservice服务中请求头truth值为:" + truth);
            return userService.getById(id);
        }
    }
    
  • Step3: 更改服务消费者(即order-service模块)的表现层OrderController类中的代码,以便于测试请求头是否添加成功,OrderController类代码如下

    • Step3-1:queryOrderById方法添加一个获取请求头信息的参数truth

    • Step3-2: 给该参数添加@RequestHeader注解,并在该注解中利用value指明要获取到的请求头的名称,同时将该注解中的required设为false,以此避免未配置请求头过滤器导致的获取请求头信息失败

      package at.guigu.controller;
      
      import at.guigu.po.Order;
      import at.guigu.service.IOrderService;
      import lombok.RequiredArgsConstructor;
      import org.springframework.web.bind.annotation.*;
      
      @RestController
      @RequestMapping("order")
      @RequiredArgsConstructor
      public class OrderController {
      
          private final IOrderService orderService;
      
          @GetMapping("{orderId}")
          public Order queryOrderById(@PathVariable("orderId") Long orderId,
                                      @RequestHeader(value = "Truth", required = false) String truth) {
              System.out.println("orderservice服务中请求头truth值为:" + truth);
              // 根据id查询订单并返回
              return orderService.queryOrderById(orderId);
          }
      }
      
  • Step4: 重新启动Gateway网关服务以及服务提供者(即user-service模块)、服务消费者(即order-service模块)的启动类,然后利用PostMan进行测试

    在这里插入图片描述

注意:

​ 在以上测试中,前端请求是localhost:10010/user/1,此时后端控制台会显示出请求头信息。但如果前端请求是localhost:10010/order/101呢?,此时结果如下图所示可知,虽然运行成功,但未能获取到请求头信息

原因:

​ 当前过滤器只写在了与userservice服务相关的路由下(即只写在了指定路由下),因此仅仅对访问userservice服务的请求有效。

在这里插入图片描述

全局路由的默认过滤器

  • 若要对所有的路由都生效,则可以将过滤器工厂写到default下。此时gateway网关服务的配置文件application.yml代码如下:

    server:
      port: 10010
    spring:
      application:
        name: gateway
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 #Nacos地址
            enabled: true
        gateway:
          routes:
            - id: user-service # 自定义路由的唯一标识(此处自定义为模块名)
              uri: lb://userservice #路由的目标地址
              predicates: # 路由断言,判断请求是否符合指定规则
                - Path=/user/** # 路径断言,判断路径是否以/user开头;若是,则符合制定规则
            - id: order-service
              uri: lb://orderservice
              predicates:
                - Path=/order/**
          default-filters: # 配置路由默认的过滤器
            # 配置请求头过滤器---设置请求头Truth=nihao
            - AddRequestHeader=Truth, nihao
      main:
        web-application-type: reactive
    logging:
      level:
        # 配置指定包及其子包下的所有类的日志级别为debug---会输出大于等于该级别的日志信息
        at.guigu: DEBUG
      # 配置日志输出的时间戳格式
      pattern:
        dateformat: MM-dd HH:mm:ss:SSS
    
  • 重新启动Gateway网关服务以及服务提供者(即user-service模块)、服务消费者(即order-service模块)的启动类,然后利用PostMan进行测试

    • 前端请求是localhost:10010/user/1

      在这里插入图片描述

    • 前端请求是localhost:10010/order/101

      在这里插入图片描述

全局过滤器GlobalFilter

Gateway网关服务虽然提供了31种路由过滤器,但是每一种过滤器的作用都是固定的,所以还存在较大局限性

  • 作用:处理一切进入网关的请求和微服务响应,与路由过滤器GatewayFilter作用一样

  • 与路由过滤器GatewayFilter的区别

    • 路由过滤器GatewayFilter是通过配置文件定义的,处理逻辑是固定的
    • 全局过滤器GlobalFilter需要自己手写代码实现GlobalFilter接口
  • 该接口代码如下

    public interface GlobalFilter {
        /**
         *  处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
         *
         * @param exchange 请求上下文,里面可以获取Request、Response等信息
         * @param chain 用来把请求委托给下一个过滤器 
         * @return {@code Mono<Void>} 返回标示当前过滤器业务结束
         */
        Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
    }
    
    • filter方法中编写自定义逻辑,可以实现下列功能:
      • 登录状态判断
      • 权限校验
      • 请求限流等
  • 全局过滤器GlobalFilter实现步骤

    • Step1:实现GlobalFilter接口
    • Step2:添加@Order注解或实现Order接口
    • Step3:编写处理逻辑

快速入门

本快速入门示例已上传至Gitee的分支global-demo中,可自行下载

需求:定义全局过滤器GlobalFilter来拦截请求,并判断请求的参数是否同时满足以下两个条件。若是则放行;反之则拦截

​ 1.参数中是否有authorization

​ 2.authorization参数值是否为admin

  • Step1: 在gateway模块的gateway包下创建一个globalfilter包,并在该包下创建一个实现GlobalFilter接口的实现类AuthorizeFilter

    在这里插入图片描述

  • Step2: 重写GlobalFilter接口中的filter方法,代码如下:

    • Step2-1: 获取请求对象
    • Step2-2: 获取包含所有请求参数的Map集合
    • Step2-3: 获取集合中第一个参数为authorization的值
    • Step2-4: 校验,若校验通过则放行;反之则进行拦截处理
    • Step2-5: 拦截处理:获取响应对象
    • Step2-6: 拦截处理:设置状态码为HttpStatus.FORBIDDEN(即401),代表请求被拦截
    • Step2-7: 拦截处理:结束处理
    • Step2-8: 给该类添加@Component注解以及@Order注解
      • @Component注解:使用在类上,用于实例化bean
      • @Order注解:用于设置过滤器的执行顺序。该注解中有一个value属性,默认值为Integer.MAX_VALUEvalue属性值越小,优先级越高。所以我们可以通过设置该注解的value属性值来设置不同全局过滤器GlobalFilter的执行顺序。博主在此处将其设置为了-1
    package at.guigu.gateway.globalfilter;
    
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.annotation.Order;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.util.MultiValueMap;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    @Order(value = -1)
    @Component
    public class AuthorizeFilter implements GlobalFilter {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 1 获取请求对象
            ServerHttpRequest request = exchange.getRequest();
            // 2 获取包含所有请求参数的Map集合
            MultiValueMap<String, String> params = request.getQueryParams();
            // 3 获取集合中第一个参数为authorization的值
            String auth = params.getFirst("authorization");
            // 4 校验
            if ("admin".equals(auth)) {
                // 放行
                return chain.filter(exchange);
            }
            // 5.拦截
            // 5.1 获取响应对象
            ServerHttpResponse response = exchange.getResponse();
            // 5.2 设置状态码为HttpStatus.FORBIDDEN(即403),代表请求被拦截
            response.setStatusCode(HttpStatus.FORBIDDEN);
            // 5.3 结束处理
            return response.setComplete();
        }
    }
    

    注意:除了使用@Order注解来设置全局过滤器GlobalFilter的执行顺序外,还可以通过继承Ordered接口并重写其中的getOrder方法来实现,此时代码如下:

    package at.guigu.gateway.globalfilter;
    
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.util.MultiValueMap;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    @Component
    public class AuthorizeFilter implements GlobalFilter, Ordered {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 1 获取请求对象
            ServerHttpRequest request = exchange.getRequest();
            // 2 获取包含所有请求参数的Map集合
            MultiValueMap<String, String> params = request.getQueryParams();
            // 3 获取集合中第一个参数为authorization的值
            String auth = params.getFirst("authorization");
            // 4 校验
            if ("admin".equals(auth)) {
                // 放行
                return chain.filter(exchange);
            }
            // 5.拦截
            // 5.1 获取响应对象
            ServerHttpResponse response = exchange.getResponse();
            // 5.2 设置状态码为HttpStatus.FORBIDDEN(即403),代表请求被拦截
            response.setStatusCode(HttpStatus.FORBIDDEN);
            // 5.3 结束处理
            return response.setComplete();
        }
    
        // 设置过滤器的执行顺序------等同于@Order注解
        @Override
        public int getOrder() {
            return -1;
        }
    }
    
  • Step3: 重新运行gateway网关服务的启动类,然后进行测试

    • 测试服务提供者(即user-service模块)

      • 前端请求为localhost:10010/user/1------未添加指定的请求参数authorization=admin

        在这里插入图片描述

      • 前端请求为localhost:10010/user/1?authorization=admin------添加了指定的请求参数

        在这里插入图片描述

    • 测试服务消费者(即order-service模块)

      • 前端请求为localhost:10010/order/101------未添加指定的请求参数authorization=admin

        在这里插入图片描述

      • 前端请求为localhost:10010/order/101?authorization=admin------添加了指定的请求参数

        在这里插入图片描述

过滤器执行顺序

  • 请求进入网关会碰到三类过滤器:

    • 当前路由的过滤器(即GatewayFilter)
    • 路由的默认过滤器(即DefaultFilter)
    • 全局过滤器(即GlobalFilter)

    前端请求路由后,Gateway网关服务会将三类过滤器合并到一个过滤器链(也就是一个List集合)中,排序后依次执行每个过滤器,如下图所示

    在这里插入图片描述

  • 排序的规则

    • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前

    • GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定

    • 当前路由过滤器(即GatewayFilter)和路由默认过滤器(即DefaultFilter)的order值由Spring指定,默认是按照声明顺序从1递增。

      如下示例代码所示,Truth=nihao的order值为1;Truth=haoa的order值为2。所以Truth=nihao的优先级高

      spring:
        cloud:
          gateway:
            routes:
              - id: user-service
                uri: lb://userservice
                predicates:
                  - Path=/user/**
            default-filters: 
              - AddRequestHeader=Truth, nihao
              - AddRequestHeader=Truth, haoa
      
    • 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。

跨域问题处理

  • 域名不一致就是跨域,主要包括:

    • 域名不同: www.taobao.comwww.taobao.orgwww.jd.commiaosha.jd.com
    • 域名相同,端口不同:localhost:8080localhost:8081
  • 在我们的服务示例中,服务消费者(即order-service模块)、服务提供者(user-service模块)的端口分别为8080和8081,它们也是跨域。但是为什么没有产生跨域问题呢?

    • 跨域问题指的是:浏览器 禁止请求的发起者与服务端发生 跨域ajax请求 ,请求被浏览器拦截的问题
    • 而服务消费者调用服务提供者并未用到浏览器,所以也就未发生跨域问题
  • 若直接通过浏览器进行跨域访问,则会产生跨域问题(报错如图所示),则网关服务处理跨域问题的解决方案是使用CORS,CORS内容具体可详见https://www.ruanyifeng.com/blog/2016/04/cors.html

    在这里插入图片描述

  • 在gateway网关服务的配置文件application.yml中配置如下内容

    • 老版本配置

      spring:
        cloud:
          gateway:
            globalcors: # 全局的跨域处理
              add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题(默认为false)
              corsConfigurations:
                '[/**]': # 配置要拦截哪些请求来进行跨域处理,此处代表拦截一切请求
                  allowedOrigins: # 配置允许跨域请求的网站 
                    - "http://localhost:8090"
                  allowedMethods: # 配置允许的跨域ajax请求的方式
                    - "GET"
                    - "POST"
                    - "DELETE"
                    - "PUT"
                    - "OPTIONS"
                  allowedHeaders: "*" # 配置允许在请求中携带的头信息
                  allowCredentials: true # 是否允许携带cookie
                  maxAge: 360000 # 配置跨域请求的有效期(单位:秒)
      
    • 新版本配置

      spring:
        cloud:
          gateway:
            globalcors: # 全局的跨域处理
              add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题(默认为false)
              cors-configurations:
                '[/**]': # 配置要拦截哪些请求来进行跨域处理,此处代表拦截一切请求
                  allowed-origins:
                    - "http://localhost:8090"
                  allowed-methods: # 配置允许跨域请求的网站 
                    - "GET"
                    - "POST"
                    - "DELETE"
                    - "PUT"
                    - "OPTIONS"
                  allowed-headers: "*" # 配置允许在请求中携带的头信息
                  allow-credentials: true # 是否允许携带cookie
                  max-age: 360000 # 配置跨域请求的有效期(单位:秒)
      

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/963893.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Bean 容器

技术成长&#xff0c;是对场景设计细节不断的雕刻&#xff01; 你觉得自己的技术什么时候得到了快速的提高&#xff0c;是CRUD写的多了以后吗&#xff1f;想都不要想&#xff0c;绝对不可能&#xff01;CRUD写的再多也只是能满足你作为一个搬砖工具人&#xff0c;敲击少逻辑流…

【react+redux】 react使用redux相关内容

首先说一下&#xff0c;文章中所提及的内容都是我自己的个人理解&#xff0c;是我理逻辑的时候&#xff0c;自我说服的方式&#xff0c;如果有问题有补充欢迎在评论区指出。 一、场景描述 为什么在react里面要使用redux&#xff0c;我的理解是因为想要使组件之间的通信更便捷…

利用腾讯云cloud studio云端免费部署deepseek-R1

1. cloud studio 1.1 cloud studio介绍 Cloud Studio&#xff08;云端 IDE&#xff09;是基于浏览器的集成式开发环境&#xff0c;为开发者提供了一个稳定的云端工作站。支持CPU与GPU的访问。用户在使用 Cloud Studio 时无需安装&#xff0c;随时随地打开浏览器即可使用。Clo…

基于VMware的ubuntu与vscode建立ssh连接

1.首先安装openssh服务 sudo apt update sudo apt install openssh-server -y 2.启动并检查ssh服务状态 到这里可以按q退出 之后输入命令 &#xff1a; ip a 红色挡住的部分就是我们要的地址&#xff0c;这里就不展示了哈 3.配置vscode 打开vscode 搜索并安装&#xff1a;…

四川正熠法律咨询有限公司正规吗可信吗?

在纷繁复杂的法律环境中&#xff0c;寻找一家值得信赖的法律服务机构是每一个企业和个人不可或缺的需求。四川正熠法律咨询有限公司&#xff0c;作为西南地区备受瞩目的法律服务提供者&#xff0c;以其专注、专业和高效的法律服务&#xff0c;成为众多客户心中的首选。 正熠法…

【优先算法】专题——位运算

在讲解位运算之前我们来总结一下常见的位运算 一、常见的位运算 1.基础为运算 << &&#xff1a;有0就是0 >> |&#xff1a;有1就是1 ~ ^&#xff1a;相同为0&#xff0c;相异位1 /无进位相加 2.给一个数 n&#xff0c;确定它的二进制表示…

Android --- handler详解

handler 理解 handler 是一套Android 消息传递机制&#xff0c;主要用于线程间通信。 tips&#xff1a; binder/socket 用于进程间通信。 参考&#xff1a; Android 进程间通信-CSDN博客 handler 就是主线程在起了一个子线程&#xff0c;子线程运行并生成message &#xff0c;l…

【线程】基于阻塞队列的生产者消费者模型

文章目录 1 生产者消费者模型2 阻塞队列2.1 成员变量2.2 消费者操作2.3 生产者生产 3 总结 1 生产者消费者模型 在多线程环境中&#xff0c;生产者消费者模型是一种经典的线程同步模型&#xff0c;用于处理生产者线程与消费者线程之间的工作调度和资源共享问题。在这个模型中&a…

解决PyG安装中torch-sparse安装失败问题:详细指南

1 问题描述 最近在学习GNN&#xff0c;需要使用PyTorch Geometric&#xff08;PyG&#xff09;库。在安装PyG的过程中&#xff0c;遇到了torch-sparse安装失败的问题&#xff0c;错误提示为&#xff1a; ERROR: Failed building wheel for torch-sparse本文将详细记录问题的解…

4 [危机13小时追踪一场GitHub投毒事件]

事件概要 自北京时间 2024.12.4 晚间6点起&#xff0c; GitHub 上不断出现“幽灵仓库”&#xff0c;仓库中没有任何代码&#xff0c;只有诱导性的病毒文件。当天&#xff0c;他们成为了 GitHub 上 star 增速最快的仓库。超过 180 个虚假僵尸账户正在传播病毒&#xff0c;等待不…

【B站保姆级视频教程:Jetson配置YOLOv11环境(六)PyTorchTorchvision安装】

Jetson配置YOLOv11环境&#xff08;6&#xff09;PyTorch&Torchvision安装 文章目录 1. 安装PyTorch1.1安装依赖项1.2 下载torch wheel 安装包1.3 安装 2. 安装torchvisiion2.1 安装依赖2.2 编译安装torchvision2.2.1 Torchvisiion版本选择2.2.2 下载torchvisiion到Downloa…

自动化软件测试的基本流程

一、自动化测试的准备 1.1 了解测试系统 首先对于需要测试的系统我们需要按照软件需求说明书明确软件功能。这里以智慧养老系统作为案例进行测试&#xff0c;先让我们看看该系统的登录界面和用户管理界面。 登录界面&#xff1a; 登录成功默认界面&#xff1a; 用户管理界面…

前端力扣刷题 | 6:hot100之 矩阵

73. 矩阵置零 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 法一&#xff1a; var setZeroes function(matrix) {let setX new Set(); // 用于存储需要置零的行索引let setY new Set(); //…

SLAM技术栈 ——《视觉SLAM十四讲》学习笔记(一)

《视觉SLAM十四讲》学习笔记&#xff08;一&#xff09; 第2讲 初识SLAM习题部分 第3讲 三维空间刚体运动3.1 左手系与右手系3.2 齐次坐标3.3 旋转矩阵与变换矩阵3.4 正交群与欧式群3.5 旋转向量与欧拉角3.6 实践Eigen线性代数库3.6.1 QR分解(QR decomposition) 3.7 四元数到其…

自动驾驶---两轮自行车的自主导航

1 背景 无人驾驶汽车最早出现在DARPA的比赛中&#xff0c;从那个时刻开始&#xff0c;逐渐引起全球学者的注意&#xff0c;于是从上个世纪开始各大高校院所开始了无人汽车的研发。直到这两年&#xff0c;无人驾驶汽车才开始走进寻常百姓家&#xff0c;虽然目前市面上的乘用车还…

Spring Boot 2 快速教程:WebFlux处理流程(五)

WebFlux请求处理流程 下面是spring mvc的请求处理流程 具体步骤&#xff1a; 第一步&#xff1a;发起请求到前端控制器(DispatcherServlet) 第二步&#xff1a;前端控制器请求HandlerMapping查找 Handler &#xff08;可以根据xml配置、注解进行查找&#xff09; 匹配条件包括…

优选算法的灵动之章:双指针专题(一)

个人主页&#xff1a;手握风云 专栏&#xff1a;算法 一、双指针算法思想 双指针算法主要用于处理数组、链表等线性数据结构中的问题。它通过设置两个指针&#xff0c;在数据结构上进行遍历和操作&#xff0c;从而实现高效解决问题。 二、算法题精讲 2.1. 查找总价格为目标值…

Intel 与 Yocto 项目的深度融合:全面解析与平台对比

在嵌入式 Linux 领域&#xff0c;Yocto 项目已成为构建定制化 Linux 发行版的事实标准&#xff0c;广泛应用于不同架构的 SoC 平台。Intel 作为 x86 架构的领导者&#xff0c;在 Yocto 生态中投入了大量资源&#xff0c;为其嵌入式处理器、FPGA 和 AI 加速硬件提供了完整的支持…

算法刷题Day29:BM67 不同路径的数目(一)

题目链接 描述 解题思路&#xff1a; 二维dp数组初始化。 dp[i][0] 1, dp[0][j] 1 。因为到达第一行第一列的每个格子只能有一条路。状态转移 dp[i][j] dp[i-1][j] dp[i][j-1] 代码&#xff1a; class Solution: def uniquePaths(self , m: int, n: int) -> int: #…

98,【6】 buuctf web [ISITDTU 2019]EasyPHP

进入靶场 代码 <?php // 高亮显示当前 PHP 文件的源代码&#xff0c;通常用于调试或展示代码&#xff0c;方便用户查看代码逻辑 highlight_file(__FILE__);// 从 GET 请求中获取名为 _ 的参数值&#xff0c;并赋值给变量 $_ // 符号用于抑制可能出现的错误信息&#xff…