• 【云原生&微服务十】SpringCloud之OpenFeign实现服务间请求头数据传递(OpenFeign拦截器RequestInterceptor的使用)


    一、前言

    在前面的文章:

    SpringCloud之Feign实现声明式客户端负载均衡详细案例

    我们聊了OpenFeign的概述、为什么会使用Feign代替Ribbon、Feign和OpenFeign的区别、以及详细的OpenFeign实现声明式客户端负载均衡案例。

    在一些业务场景中,微服务间相互调用需要做鉴权,以保证我们服务的安全性。即:服务A调用服务B的时候需要将服务B的一些鉴权信息传递给服务B,从而保证服务B的调用也可以通过鉴权,进而保证整个服务调用链的安全。

    本文我们就讨论如果通过openfeign的拦截器RequestInterceptor实现服务调用链中上下游服务请求头数据的传递。

    二、实现RequestInterceptor

    通过RequestInterceptor 拦截器拦截我们的openfeign服务请求,将上游服务的请求头或者请求体中的数据封装到我们的openfeign调用的请求模板中,从而实现上游数据的传递。

    1、RequestInterceptor实现类

    1)RequestInterceptor实现类

    package com.saint.feign.config;
    
    import feign.RequestInterceptor;
    import feign.RequestTemplate;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.context.request.RequestAttributes;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.Enumeration;
    import java.util.Objects;
    
    /**
     * 自定义的Feign拦截器
     *
     * @author Saint
     */
    @Slf4j
    public class MyFeignRequestInterceptor implements RequestInterceptor {
        /**
         * 这里可以实现对请求的拦截,对请求添加一些额外信息之类的
         *
         * @param requestTemplate
         */
        @Override
        public void apply(RequestTemplate requestTemplate) {
            // 1. obtain request
            final ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    
            // 2. 兼容hystrix限流后,获取不到ServletRequestAttributes的问题(使拦截器直接失效)
            if (Objects.isNull(attributes)) {
                log.error("MyFeignRequestInterceptor is invalid!");
                return;
            }
            HttpServletRequest request = attributes.getRequest();
    
            // 2. obtain request headers,and put it into openFeign RequestTemplate
            Enumeration<String> headerNames = request.getHeaderNames();
            if (Objects.nonNull(headerNames)) {
                while (headerNames.hasMoreElements()) {
                    String name = headerNames.nextElement();
                    String value = request.getHeader(name);
                    requestTemplate.header(name, value);
                }
            }
    
            // todo 需要传递请求参数时放开
            // 3. obtain request body, and put it into openFeign RequestTemplate
    //        Enumeration<String> bodyNames = request.getParameterNames();
    //        StringBuffer body = new StringBuffer();
    //        if (bodyNames != null) {
    //            while (bodyNames.hasMoreElements()) {
    //                String name = bodyNames.nextElement();
    //                String value = request.getParameter(name);
    //                body.append(name).append("=").append(value).append("&");
    //            }
    //        }
    //        if (body.length() != 0) {
    //            body.deleteCharAt(body.length() - 1);
    //            requestTemplate.body(body.toString());
    //            log.info("openfeign interceptor body:{}", body.toString());
    //        }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    2)使RequestInterceptor生效(均已验证)

    使RequestInterceptor生效的方式有四种;

    1> 代码方式全局生效

    直接在Spring可以扫描到的路径使用@Bean方法将RequestInterceptor实现类注入到Spring容器;

    package com.saint.feign.config;
    
    import feign.RequestInterceptor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @author Saint
     */
    @Configuration
    public class MyConfiguration {
    
        @Bean
        public RequestInterceptor requestInterceptor() {
            return new MyFeignRequestInterceptor();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2> 配置方式全局生效

    feign:
      client:
        config:
          default:
            connectTimeout: 5000
            readTimeout: 5000
            loggerLevel: full
            # 拦截器配置(和@Bean的方式二选一)
            requestInterceptors:
              - com.saint.feign.config.MyFeignRequestInterceptor
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3> 代码方式针对某个服务生效

    直接在@FeignClient注解中指定configuration属性为RequestInterceptor实现类

    在这里插入图片描述

    4、配置方式针对某个服务生效

    feign:
      client:
        config:
          SERVICE-A:
            connectTimeout: 5000
            readTimeout: 5000
            loggerLevel: full
            # 拦截器配置(和@Bean的方式二选一)
            requestInterceptors:
              - com.saint.feign.config.MyFeignRequestInterceptor
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2、效果验证

    1)feign-server服务改造

    在文章 SpringCloud之Feign实现声明式客户端负载均衡详细案例的基础下,我们修改feign-server项目,添加一个MVC拦截器(用于获取请求头中的数据)
    在这里插入图片描述

    1> MvcInterceptor

    package com.saint.feign.config;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * 自定义MVC拦截器
     *
     * @author Saint
     */
    @Slf4j
    public class MvcInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
            String token = request.getHeader("token-saint");
            log.info("obtain token is : {}", token);
            return true;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2> MvcInterceptorConfig

    设置MVC拦截器会拦截哪些路径的请求,这里是所有的请求全部拦截。

    package com.saint.feign.config;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    /**
     * MVC拦截器配置
     *
     * @author Saint
     */
    @Configuration
    public class MvcInterceptorConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new MvcInterceptor())
                    .addPathPatterns("/**");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2)结果验证

    1> 执行请求:
    在这里插入图片描述在这里插入图片描述

    2> feign-consumer中的日志:
    在这里插入图片描述

    3> feign-server中的日志:
    在这里插入图片描述

    结果显示,RequestInterceptor生效了

    三、结合Hystrix限流使用时的坑(仅做记录)

    此处OpenFeign依赖的SpringCloud版本是2020.X之前。

    在application.yaml文件中做如下配置开启了Hystrix限流:

    feign:
      hystrix:
        enabled: true
    hystrix:
      command:
        default:
          execution:
            isolation:
              thread:
                timeoutInMilliseconds: 30000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    做完上述配置后,Feign接口的熔断机制为:线程模式

    如果我们自定义了一个RequestInterceptor实现类,就会导致hystrix熔断机制失效,接口调用异常(404、null);

    1、原因分析

    • 在feign调用之前,会走RequestInterceptor拦截器,拦截器中使用了ServletRequestAttributes获取请求数据;
    • 默认feign使用的是线程池模式,当开启熔断的时候,负责熔断的线程和执行Feign接口的线程不是同一个线程,ServletRequestAttributes取到的将会是空值。

    2、解决方案

    将hystrix熔断方式从线程模式改为信号量模式;

    feign:
      hystrix:
        enabled: true
    hystrix:
      command:
        default:
          execution:
            isolation:
              thread:
                timeoutInMilliseconds: 30000
              strategy: SEMAPHORE
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3、Hystrix线程和信号量隔离区别

    在这里插入图片描述

    4、线程和信号量隔离的使用场景?

    1> 线程池隔离

    • 请求并发量大,并且耗时长(一般是计算量大或者读数据库);
    • 采用线程池隔离,可以保证大量的容器线程可用,不会由于其他服务原因,一直处于阻塞或者等待状态,快速失败返回。
      2> 信号量隔离
    • 请求并发量大,并且耗时短(一般是计算量小,或读缓存);
    • 采用信号量隔离时的服务的返回往往非常快,不会占用容器线程太长时间;
    • 其减少了线程切换的一些开销,提高了缓存服务的效率 。
  • 相关阅读:
    大盘向好、交付企稳,金科股份中报里的关键信号
    [深入研究4G/5G/6G专题-45]: L3信令控制-1-软件功能和整体架构
    “购物返现积分兑换”——区块链思维的购物返利方式
    String Boot项目加密混淆组件xjar+allatori组合使用
    Python小技巧:两行代码实现批量给图片填加水印,这也太简单了~
    H3C S7000/S7500E/10500系列交换机Console密码忘记处理方法
    智慧食堂到底“智”在哪里?解决传统食堂5大问题
    Flink Table Store v0.2 应用场景和核心功能
    【毕业设计源码】基于微信小程序的第二课堂设计与实现
    Docker 常用命令
  • 原文地址:https://blog.csdn.net/Saintmm/article/details/125532945