子龙 子龙
首页
学习指南
工具
AI副业
开源项目
技术书籍

程序员子龙

Java 开发从业者
首页
学习指南
工具
AI副业
开源项目
技术书籍
  • 基础

  • JVM

  • Spring

  • 并发编程

  • Mybatis

  • 网络编程

  • 数据库

  • 缓存

  • 设计模式

  • 分布式

  • 高并发

  • SpringBoot

  • SpringCloudAlibaba

    • Spring Cloud 入门篇
    • SpringCloud 注册中心
    • SpringCloud Nacos
    • Spring Cloud Gateway
    • Spring Security 整合OAuth2
    • oauth 整合 jwt
    • 单点登录
    • Spring Cloud Feign
      • Spring Cloud Nacos Config
      • openfeign远程调用异常统一处理
      • Spring Cloud Ribbon
      • sentinel 实战
      • SpringCloud集成 报错 An attempt was made to call a method that does not exist
      • 什么是jwt
    • Nginx

    • 面试

    • 生产问题

    • 系统设计

    • 消息中间件

    • Java
    • SpringCloudAlibaba
    程序员子龙
    2024-01-29
    目录

    Spring Cloud Feign

    # 什么是Feign

    Feign是Netflix开发的声明式、模板化的HTTP客户端。 Feign可帮助我们更加便捷、优雅地调用HTTP API。

    Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。

    Spring Cloud openfeign对Feign进行了增强,使其支持Spring MVC注解,另外还整合了Ribbon和Eureka,从而使得Feign的使用更加方便。

    Feign是一个声明式的web服务客户端,让编写web服务客户端变得非常容易, 只需创建一个接口并在接口上添加注解即可。

    Feign可以做到使用 HTTP 请求远程服务时就像调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个 HTTP 请求。它像 Dubbo 一样,consumer 直接调用接口方法调用 provider,而不需要通过常规的 Http Client 构造请求再解析返回数据。它解决了让开发者调用远程接口就跟调用本地方法一样,无需关注与远程的交互细节,更无需关注分布式环境开发。

    OpenFeign和Feign的区别: Feign是SpringCloud中的一个轻量级RestFul的Http客户端,内置了Ribbon,用于客户端负载均衡,使用方法是使用Feign的注解去修饰一个接口,客户端调用这个接口那么久是调用远程的微服务了。 OpenFeign在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等。OpenFeign的@FeigenClient注解可以解析SpringMvc的@RequestMapping注解下的接口,通过动态代理的方式产生实现类,实现类中进行负载均衡的微服务远程调用!

    # Spring Cloud Alibaba快速整合Feign

    引入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
    1
    2
    3
    4

    编写调用接口+@FeignClient注解,实际开发中,最好单独定义成一个模块,方便引用

    @FeignClient(value = "order",path = "/order")
    public interface OrderFeignService {
    
        @RequestMapping("/findOrderByUserId/{userId}")
        R findOrderByUserId(@PathVariable("userId") Integer userId);
    
    }
    
    1
    2
    3
    4
    5
    6
    7

    调用端在启动类上添加@EnableFeignClients注解,@EnableFeignClients:启用feign进行远程调用

    @EnableFeignClients
    public class FeignDemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(FeignDemoApplication.class, args);
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    4)发起调用,像调用本地方式一样调用远程服务

    @Autowired
    OrderFeignService orderFeignService;
    
    @RequestMapping(value = "/findOrderByUserId/{id}")
    public R  findOrderByUserId(@PathVariable("id") Integer id) {
        //feign调用
        R result = orderFeignService.findOrderByUserId(id);
        return result;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    # spring 通过@Autowired为什么找不到feign接口

    1、没有使用@EnableFeignClients进行扫描;

    2、使用了@EnableFeignClients进行扫描,但是该注解没有添加basePackages属性,默认扫描的是当前启动类所在的包及其子包下feign,而使用的feign是第三方提供的,不在扫描路径下;

    3、使用了@EnableFeignClients进行扫描,也添加了basePackages属性配置了第三方feign接口所在包路径,但是feign接口定义的方法中,没有添加@RequestMapping等注解;

    # 修改负载均衡策略

    feign其实不是做负载均衡的,负载均衡是ribbon的功能。

    注意下面的代码不能在springboot默认的扫描包路劲下。

    @Bean
    public IRule getRule() {
        return new RandomRule();
    }
    
    1
    2
    3
    4

    # 日志配置

    # 全局配置

    @Configuration  // 全局配置
    public class FeignConfig {
        /**
         * 日志级别
         * 通过源码可以看到日志等级有 4 种,分别是:
         * NONE:不输出日志。
         * BASIC:只输出请求方法的 URL 和响应的状态码以及接口执行的时间。
         * HEADERS:将 BASIC 信息和请求头信息输出。
         * FULL:输出完整的请求信息。
         *
         * @return
         */
        @Bean
        public Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    日志输出:

    2022-04-11 15:40:46.731 DEBUG 29944 --- [nio-8055-exec-4] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] ---> GET http://order/order/findOrderByUserId/2 HTTP/1.1 2022-04-11 15:40:46.732 DEBUG 29944 --- [nio-8055-exec-4] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] Accept-Encoding: gzip 2022-04-11 15:40:46.732 DEBUG 29944 --- [nio-8055-exec-4] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] Accept-Encoding: deflate 2022-04-11 15:40:46.732 DEBUG 29944 --- [nio-8055-exec-4] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] Authorization: 249df155-e75f-4a4e-b682-aee20e4e9d13 2022-04-11 15:40:46.732 DEBUG 29944 --- [nio-8055-exec-4] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] ---> END HTTP (0-byte body) 2022-04-11 15:40:47.836 DEBUG 29944 --- [nio-8055-exec-4] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] <--- HTTP/1.1 200 (1104ms) 2022-04-11 15:40:47.836 DEBUG 29944 --- [nio-8055-exec-4] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] connection: keep-alive 2022-04-11 15:40:47.837 DEBUG 29944 --- [nio-8055-exec-4] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] content-type: application/json 2022-04-11 15:40:47.837 DEBUG 29944 --- [nio-8055-exec-4] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] date: Mon, 11 Apr 2022 07:40:47 GMT 2022-04-11 15:40:47.837 DEBUG 29944 --- [nio-8055-exec-4] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] keep-alive: timeout=60 2022-04-11 15:40:47.837 DEBUG 29944 --- [nio-8055-exec-4] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] transfer-encoding: chunked 2022-04-11 15:40:47.837 DEBUG 29944 --- [nio-8055-exec-4] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] 2022-04-11 15:40:47.862 DEBUG 29944 --- [nio-8055-exec-4] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] {"msg":"success","code":0,"orders":[{"id":2,"userId":"2","commodityCode":"2","count":0,"amount":0}]} 2022-04-11 15:40:47.863 DEBUG 29944 --- [nio-8055-exec-4] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] <--- END HTTP (100-byte body)

    生产环境中,尽量不要配置成FULL,配置成BASIC

    2022-04-11 15:43:11.487 DEBUG 1736 --- [nio-8055-exec-3] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] ---> GET http://order/order/findOrderByUserId/2 HTTP/1.1 2022-04-11 15:43:11.571 DEBUG 1736 --- [nio-8055-exec-3] c.t.feigndemo.feign.OrderFeignService : [OrderFeignService#findOrderByUserId] <--- HTTP/1.1 200 (82ms)

    # 局部配置

    feign:
      client:
        config:
          order:  #对应微服务
            loggerLevel: FULL
    
    1
    2
    3
    4
    5

    # 接口鉴权认证

    通常调用的接口都是有权限控制的,很多时候可能认证的值是通过参数去传递的,还有就是通过请求头去传递认证信息,比如 Basic 认证方式。

    每次 feign 发起http调用之前,会去执行拦截器中的逻辑。

    消费端配置拦截器

    public class FeignAuthRequestInterceptor implements RequestInterceptor {
        @Override
        public void apply(RequestTemplate template) {
            // 业务逻辑  模拟认证逻辑
              String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            Random random=new Random();
            StringBuffer sb=new StringBuffer();
            for(int i=0;i<10;i++) {
                int number = random.nextInt(62);
                sb.append(str.charAt(number));
            }
    
            String access_token = sb.toString();
            template.header("Authorization",access_token);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    注册拦截器

    @Configuration
    public class WebMvcConfig extends WebMvcConfigurationSupport {
        
        @Override
        protected void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new FeignAuthRequestInterceptor());
        }
        
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    服务端配置拦截器

    @Slf4j
    public class AuthInterceptor implements HandlerInterceptor {
        
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
            boolean flag = true;
            // 简单的认证逻辑  从请求头中获取Authorization
            String authorization = request.getHeader("Authorization");
            log.info("=========Authorization:"+authorization);
            if (StringUtils.isEmpty(authorization)){
                // 从请求参数中获取access_token
                String access_token = request.getParameter("access_token");
                // TODO 实现逻辑
                if(StringUtils.isEmpty(access_token)){
                    flag = false;
                }
            }
            return flag;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    注册拦截器

    @Configuration
    public class WebMvcConfig extends WebMvcConfigurationSupport {
        
        @Override
        protected void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new AuthInterceptor());
        }
        
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    这样在请求时候能收到认证信息

    2022-04-11 16:23:59.703 INFO 12216 --- [io-8021-exec-10] com.test.interceptor.AuthInterceptor : =========Authorization:XKBuFhAiIh

    注册拦截器也可以通过拦截器

    # 接口超时配置

    全局配置

    @Configuration
    public class FeignConfig {
        @Bean
        public Request.Options options() {
            // connectTimeoutMillis readTimeoutMillis
            return new Request.Options(5000, 5000);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    局部配置,针对不同微服务

    feign:
      client:
        config:
          order:  #对应微服务
            requestInterceptors[0]:  #配置拦截器
              com.test.feigndemo.interceptor.FeignAuthRequestInterceptor
            # 连接超时时间,默认2s
            connectTimeout: 3000
            # 请求处理超时时间,默认5s
            readTimeout: 1000
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    # 重试配置

    Feign在调用外部服务的时候,偶尔会出现 **SocketException: Connection reset 、NoHttpResponseException: xxxxxx failed to respond 等异常,**而这些异常并非是因为服务提供方服务不可用,或者负载过高引起的,百度查了查大概原因说是因为在Client 和Server建立链接之后Server端在数据为正常响应之前就关闭了链接导致的异常。

    Feign 调用发起请求处理类 SynchronousMethodHandler 重试代码如下:

    Retryer 则是 Feign 的重试策略接口,默认Feign 配置的重试策略是 Retryer.NEVER_RETRY 也就是不走重试,直接异常报错。

    需要注意几点: 1.Feign默认配置是不走重试策略的,当发生RetryableException异常时直接抛出异常。 2.并非所有的异常都会触发重试策略,只有 RetryableException 异常才会触发异常策略。 3.在默认Feign配置情况下,只有在网络调用时发生 IOException 异常时,才会抛出 RetryableException,也是就是说链接超时、读超时等不不会触发此异常。

    因此常见的 SocketException、NoHttpResponseException、UnknownHostException、HttpRetryException、SocketConnectException、ConnectionClosedException 等异常都可触发Feign重试机制。

    # 默认重试设置

    全局配置:

        @Bean
        public Retryer feignRetryer() {
            //最大请求次数为5,初始间隔时间为100ms,下次间隔时间1.5倍递增,重试间最大间隔时间为1s,
            return new Retryer.Default();
        }
    
    1
    2
    3
    4
    5

    # 自定义重试配置

    局部配置:

    import feign.RetryableException;
    import feign.Retryer;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    
    import static java.util.concurrent.TimeUnit.SECONDS;
    
    @Slf4j
    @Component
    public  class CommonFeignRetry extends Retryer.Default {
    
    
        public CommonFeignRetry() {
            //重试10次 最大间隔时间1秒
            this(100, SECONDS.toMillis(1), 10);
        }
     
        public CommonFeignRetry(long period, long maxPeriod, int maxAttempts) {
            super(period, maxPeriod, maxAttempts);
        }
    
        @Override
        public void continueOrPropagate(RetryableException e) {
    
            log.warn("【FeignRetryAble】Message【{}】", e.getMessage());
            super.continueOrPropagate(e);
        }
    
     
        @Override
        public Retryer clone() {
            return new CommonFeignRetry();
        }
    }
    
    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

    指定FeignClient走重试策略:

    }
    @FeignClient(name = "test-service", contextId = "testClient",
    fallbackFactory = FmsBaseClientFallbackFactory.class, configuration = CommonFeignRetryConfig.class)
    
    1
    2
    3

    也可以在配置文件中指定:

    feign:
      client:
        config:
          feignName:
            connectTimeout: 5000
            readTimeout: 5000
            loggerLevel: full
            errorDecoder: com.example.SimpleErrorDecoder
            retryer: com.example.SimpleRetryer
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    全局配置:

       @Bean
        Retryer getRetry() {
            return new CommonFeignRetry();
        }
    
    
    1
    2
    3
    4
    5

    # 客户端组件配置

    Feign 中默认使用 JDK 原生的 URLConnection 发送 HTTP 请求,可以集成别的组件来替换掉 URLConnection,比如 Apache HttpClient,OkHttp。

    # 配置Apache HttpClient

    <!-- Apache HttpClient -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.7</version>
    </dependency>
    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-httpclient</artifactId>
        <version>10.1.0</version>
    </dependency>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    原理是 spring boot 自动装配

    //org.springframework.cloud.openfeign.FeignAutoConfiguration
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(ApacheHttpClient.class)
    @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
    @ConditionalOnMissingBean(CloseableHttpClient.class)
    @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
    protected static class HttpClientFeignConfiguration {
    
    1
    2
    3
    4
    5
    6
    7

    # 配置 OkHttp

    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-okhttp</artifactId>
    </dependency>
    
    1
    2
    3
    4
    feign:
      #feign 使用 okhttp
      httpclient:
        enabled: false
      okhttp:
        enabled: true
    
    1
    2
    3
    4
    5
    6

    源码:

    //org.springframework.cloud.openfeign.FeignAutoConfiguration
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(OkHttpClient.class)
    @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
    @ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
    @ConditionalOnProperty("feign.okhttp.enabled")
    protected static class OkHttpFeignConfiguration {
    
    1
    2
    3
    4
    5
    6
    7

    # 压缩配置

    开启压缩可以有效节约网络资源,提升接口性能,我们可以配置 GZIP 来压缩数据

    feign:
        compression:
          request:
            enabled: true
            # 配置压缩的类型
            mime-types: text/xml,application/xml,application/json
            # 最小压缩值
            min-request-size: 2048
          response:
            enabled: true
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    # 编码器解码器配置

    Feign 中提供了自定义的编码解码器设置,同时也提供了多种编码器的实现,比如 Gson、Jaxb、Jackson。我们可以用不同的编码解码器来处理数据的传输。如果你想传输 XML 格式的数据,可以自定义 XML 编码解码器来实现获取使用官方提供的 Jaxb。

    @Bean
    public Decoder decoder() {
        return new JacksonDecoder();
    }
    @Bean
    public Encoder encoder() {
        return new JacksonEncoder();
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    yml配置方式

    feign:
      client:
        config:
          order:  #对应微服务
    
            # 配置编解码器
            encoder: feign.jackson.JacksonEncoder
            decoder: feign.jackson.JacksonDecoder
    
    1
    2
    3
    4
    5
    6
    7
    8
    上次更新: 2024/01/30, 15:08:57
    单点登录
    Spring Cloud Nacos Config

    ← 单点登录 Spring Cloud Nacos Config→

    最近更新
    01
    保姆级教程 用DeepSeek+飞书,批量写文案、写文章,太高效了
    06-06
    02
    还在为整理视频思维导图发愁?2 种超实用技巧,让你 10 分钟搞定,高效又省心!
    06-06
    03
    熬夜做PPT?AI一键生成高逼格幻灯片,效率提升10倍!
    06-06
    更多文章>
    Theme by Vdoing | Copyright © 2024-2025

        辽ICP备2023001503号-2

    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式