当前位置 : 首页 » 文章分类 :  开发  »  Spring-Cloud-OpenFeign

Spring-Cloud-OpenFeign

Spring Cloud Feign 笔记

Feign是一个声明式的REST客户端,它的目的就是让REST调用更加简单。
Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。
而Feign则会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。
SpringCloud对Feign进行了封装,使其支持SpringMVC标准注解和HttpMessageConverters。
Feign可以与Eureka和Ribbon组合使用以支持负载均衡。


Spring Boot 2.x中使用feign

从 Spring Boot 2.x 开始, 或者说 Spring Cloud Finchley 开始
feign starter 改名了
spring-cloud-starter-feign -> spring-cloud-starter-openfeign

同时改名的还有以下 starter
spring-cloud-starter-eureka-server —> spring-cloud-starter-netflix-eureka-server
spring-cloud-starter-eureka —> spring-cloud-starter-netflix-eureka-client
spring-cloud-starter-ribbon —> spring-cloud-starter-netflix-ribbon
spring-cloud-starter-hystrix —>spring-cloud-starter-netflix-hystrix
spring-cloud-starter-hystrix-dashboard —> spring-cloud-starter-netflix-hystrix-dashboard
spring-cloud-starter-turbine —> spring-cloud-starter-netflix-turbine
spring-cloud-starter-turbine-stream –> spring-cloud-starter-netflix-turbine-stream
spring-cloud-starter-feign —> spring-cloud-starter-openfeign
spring-cloud-starter-zuul —> spring-cloud-starter-netflix-zuul


@EnableFeignClients

@EnableFeignClients 的作用是扫描所有注解 @FeignClient 定义的feign客户端,生成其实现类。

value/basePackage

value和basePackage具有相同的功能,其中value是basePackage的别名。分别如下:
basePackage:设置自动扫描带有@FeignClient注解的基础包路径。例如:com.huangx;
value:为basePackages属性的别名,允许使用更简洁的书写方式。例如:@EnableFeignClients({“com.huangx”})
value和basePackage只能同时使用一个。

实例:下面将自动扫描 com.forezp.service 和 com.forezp.service2 包下面所有被 @FeignClient 注解修饰的类。代码如下:

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients({"com.forezp.service", "com.forezp.service2"})
public class ServiceFeignApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceFeignApplication.class, args);
    }
}

我们也可以使用basePackage进行设置,代码如下:

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.forezp.service", "com.forezp.service2"})
public class ServiceFeignApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceFeignApplication.class, args);
    }
}

defaultConfiguration

该属性用来自定义所有Feign客户端的配置,使用 @Configuration 进行配置。
当然也可以为某一个Feign客户端进行配置。具体配置方法见 @FeignClient的configuration 属性。

clients

设置由@FeignClient注解修饰的类列表。如果clients不是空数组,则不通过类路径自动扫描功能来加载FeignClient。实例:

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(clients = {SchedualServiceHi.class, SchedualServiceHi2.class})
public class ServiceFeignApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceFeignApplication.class, args);
    }
}

上面代码中引入FeignClient客户端SchedualServiceHi,且也只引入该FeignClient客户端。


@FeignClient注解

@FeignClient 注解被 @Target(ElementType.TYPE) 修饰,表示 FeignClient 注解的作用目标在接口上
例如

@FeignClient(name = "github-client",
        url = "https://api.github.com",
        configuration = GitHubExampleConfig.class,
        fallback = GitHubClient.DefaultFallback.class)
public interface GitHubClient {
    @RequestMapping(value = "/search/repositories", method = RequestMethod.GET)
    String searchRepo(@RequestParam("q") String queryStr);

    /**
     * 容错处理类,当调用失败时,简单返回空字符串
     */
    @Component
    public class DefaultFallback implements GitHubClient {
        @Override
        public String searchRepo(@RequestParam("q") String queryStr) {
            return "";
        }
    }
}

声明接口之后,在代码中通过 @Autowired 注入 GitHubClient 接口的实例即可使用。

@FeignClient 注解的常用属性如下:
path: 定义当前FeignClient的统一前缀
decode404: 当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码

name 指定客户端名字

name: 指定 FeignClient 的名称,如果项目使用了 Eureka 服务注册 ,name 是在注册中心注册的服务的名词,大小写无关。如果没有使用服务注册,则name随意。

url 手动指定服务地址

url: url用于手动指定 @FeignClient 调用的地址,常用于调用非服务注册时的接口调用,比如调用已有的第三方http接口,使用服务注册和自动发现时不需要url。可使用配置,例如 url = "${serviceA.url}"

configuration feign配置类

configuration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract, 默认为 org.springframework.cloud.netflix.feign.FeignClientsConfiguration


Hystrix断路器(fallback)

application.yml 中开启 hystrix 即可,由于 feign 中已经包含了 hystrix ,不需要额外单独再引入 hystrix 依赖包。

# 开启Hystrix
feign.hystrix.enabled: true

feign 中给 client 接口增加 fallback 有两种方式,都是 @FeignClient 注解的参数:
1、 通过 fallback 参数指定具体的 fallback 类,这种方式无法在 fallback 中获取引起 fallback 的异常信息。
2、 通过 fallbackFactory 参数指定 fallback 工厂,这种方式可获取引起 fallback 的异常信息,推荐。

指定fallback类

指定 fallback 类的方式示例如下:

@FeignClient(name = "hello", fallback = HystrixClientFallback.class)
protected interface HystrixClient {
    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello iFailSometimes();
}

static class HystrixClientFallback implements HystrixClient {
    @Override
    public Hello iFailSometimes() {
        return new Hello("fallback");
    }
}

指定fallbackFactory工厂

指定 fallbackFactory 工厂的方式如下:

@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class)
protected interface HystrixClient {
    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello iFailSometimes();
}

@Component
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {
    @Override
    public HystrixClient create(Throwable cause) {
        return new HystrixClient() {
            @Override
            public Hello iFailSometimes() {
                return new Hello("fallback; reason was: " + cause.getMessage());
            }
        };
    }
}

1.5. Feign Hystrix Fallbacks
https://cloud.spring.io/spring-cloud-static/spring-cloud-openfeign/2.2.2.RELEASE/reference/html/#spring-cloud-feign-hystrix


Feign中使用Hystrix做超时熔断降级实例

开启 feign 的 hystrix

# 开启Hystrix
feign.hystrix.enabled: true

GisClient 用于调接口获取城市列表
为了简单,直接把 Configuration 和 FallbackFactory 写成 client 接口的内部类了。

@FeignClient(name = "gis", url = "http://api.masikkk.com", configuration = GisFeignConfig.class, fallbackFactory = GisClientFallbackFactory.class)
public interface GisClient {
    // 获取城市列表
    @GetMapping(value = "/gis/v1/cities")
    GetCityListResponse getCityList();

    @Slf4j
    @Component
    class GisClientFallbackFactory implements FallbackFactory<GisClient> {
        @Override
        public GisClient create(Throwable cause) {
            return new GisClient() {
                @Override
                public GetCityListResponse getCityList() {
                    // 返回写死的默认list
                    log.warn("Fallback to call GIS getCityList, cause: {}", cause.getMessage());
                    GetCityListResponse response = new GetCityListResponse();
                    List<City> cityList = Lists.newArrayList();
                    cityList.add(City.builder().areaCode("429000").city("湖北省直辖县").province("湖北省").build());
                    cityList.add(City.builder().areaCode("469000").city("海南省直辖县").province("海南省").build());
                    cityList.add(City.builder().areaCode("500200").city("重庆市直辖县").province("重庆市").build());
                    cityList.add(City.builder().areaCode("659000").city("新疆维吾尔族自治区直辖县").province("新疆维吾尔自治区").build());
                    cityList.add(City.builder().areaCode("419000").city("河南省直辖县").province("河南省").build());
                    response.setData(cityList);
                    log.info("[GISClient#getCityList] fallback response: {}", JSONUtils.writeValue(response));
                    return response;
                }
            };
        }
    }

    @Configuration
    class GisFeignConfig extends FeignConfig {
        // 每个请求的超时时间,connect最多100毫秒,read最多100毫秒
        @Bean
        public Request.Options options() {
            return new Request.Options(100, 100);
        }
    }
}

HystrixRuntimeException xxxClient#xxxMethod() failed and no fallback available

Feign 中使用 Hystrix 报如下错误,大致意思是接口超时,但是我已经在 feign config 中配置了 Request.Options 一个比较长的时间,但不管用,还是报这个错。

HystrixRuntimeException: PeopleClient#queryEmployees(String,String) failed and no fallback available.
HystrixRuntimeException: PeopleClient#queryEmployees(Map) timed-out and no fallback available.

Hystrix 默认超时时间是1000ms,为避免出上述错误可以在 application.yml 进行如下配置。

1、设置 Hystrix 全局超时时间为一个更大的值

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 15000

2、把 Hystrix 超时熔断关闭

hystrix.command.default.execution.timeout.enabled: false

3、禁用 feign 的 hystrix

feign.hystrix.enabled: false

4、针对某个接口的某个方法关闭超时降级

# 关闭peopleclient的超时熔断
hystrix:
  command:
    PeopleClient#queryEmployees(Map).execution.timeout.enabled: false
    PeopleClient#queryEmployees(String,String).execution.timeout.enabled: false

5、针对某个接口的某个方法设置超时时间

# 关闭peopleclient的超时熔断
hystrix:
  command:
    PeopleClient#queryEmployees(Map).execution.isolation.thread.timeoutInMilliseconds: 15000
    PeopleClient#queryEmployees(String,String).execution.isolation.thread.timeoutInMilliseconds: 15000

设置header

1、 通过直接在请求上,或者在类上添加Headers的注解

@Headers({"Content-Type: application/json","Accept: application/json",Accept {contentType}})
@PostMapping(value = "/card-blank/batch-create")
Response batchCreateCard(@RequestBody CreateCardBlankDTO condition,@Param("contentType") String type);

使用 {contentType} 可以传递动态header属性

2、 通过实现RequestInterceptor接口,完成对所有的Feign请求,设置Header

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;

@Component
public class FeignInterceptor implements RequestInterceptor{
    public void apply(RequestTemplate requestTemplate){
        requestTemplate.header("hotelId", "111111");
    }
}

FeignClient 配置

org.springframework.cloud.netflix.feign.FeignClientsConfiguration 中是默认配置
bean 上有 @ConditionalOnMissingBean 注解的,表示仅当上下文中不存在该实例时才实例化,如果定义了自己的该类实例会覆盖默认值。

Encoder object转http_request

Encoder
编码器,将一个对象转换成http请求体中, Spring Cloud Feign 使用 SpringEncoder

feign支持form-url-encoded

@FeignClient(name = 'client', url = 'localhost:9080', path ='/rest', configuration = CoreFeignConfiguration)
interface CoreClient {

    @RequestMapping(value = '/business', method = POST, consumes = MediaType.APPLICATION_FORM_URLENCODED)
    @Headers('Content-Type: application/x-www-form-urlencoded')
    void activate(Map<String, ?> formParams)

    public static class CoreFeignConfiguration {

      @Autowired
      private ObjectFactory<HttpMessageConverters> messageConverters

      @Bean
      public Encoder encoder() {
          return new FormEncoder();
      }
    }
}

spring boot 2.0以上,FormEncoder 在spring-cloud-starter-openfeign中
spring boot 2.0以下,需单独引入 feign-form-spring

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<dependency>
  <groupId>io.github.openfeign.form</groupId>
  <artifactId>feign-form-spring</artifactId>
  <version>3.4.1</version>
</dependency>

How to POST form-url-encoded data with Spring Cloud Feign
https://stackoverflow.com/questions/35803093/how-to-post-form-url-encoded-data-with-spring-cloud-feign


Decoder http_response转object

Decoder
解码器, 将一个http响应转换成一个对象,Spring Cloud Feign 使用 ResponseEntityDecoder

Retryer 重试策略

Retryer
重试策略 Retryer.NEVER_RETRY

Feign.Builder
Feign接口构建类,覆盖默认Feign.Builder,比如:HystrixFeign.Builder

FeignLoggerFactory 日志工厂

FeignLoggerFactory 日志工厂
默认为 DefaultFeignLoggerFactory

Logger.Level 日志级别

Logger.Level 日志级别
NONE, No logging (DEFAULT).
BASIC, Log only the request method and URL and the response status code and execution time.
HEADERS, Log the basic information along with request and response headers.
FULL, Log the headers, body, and metadata for both requests and responses.


Client http客户端

Feign 在默认情况下使用的是 JDK 原生的 HttpURLConnection 发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用 HTTP 的 persistence connection。
我们可以用 Apache 的 HttpClient 替换 Feign 原始的 HTTP Client,通过设置连接池、超时时间等对服务之间的调用调优。
Spring Cloud 从 Brixton.SR5 版本开始支持这种替换。

HttpURLConnection

默认 http 客户端, 在 feign.Client 接口中有个 Default 实现类,使用 java 原生的 HttpURLConnection

feign.Client 接口的实现类
Http客户端接口,默认是 Client.Default ,但是我们是不使用它的默认实现,Spring Cloud Feign为我们提供了 okhttp3 和 ApacheHttpClient 两种实现方式,只需使用maven引入以下两个中的一个依赖即可,版本自由选择。

HttpClient

OKHttp

OKHttp 是现在比较常用的一个 HTTP 客户端访问工具,具有以下特点:
支持 SPDY,可以合并多个到同一个主机的请求。
使用连接池技术减少请求的延迟(如果SPDY是可用的话)。
使用 GZIP 压缩减少传输的数据量。
缓存响应避免重复的网络请求。


Request.Options 每个请求的超时时间

// 每个请求的超时时间
@Bean
public Request.Options options() {
    return new Request.Options(5 * 1000, 6 * 1000);
}

ErrorDecoder

当调用服务时,如果服务返回的状态码不是200,就会进入到 Feign 的 ErrorDecoder 中,因此如果我们要解析异常信息,就要重写 ErrorDecoder

RequestInterceptor 请求拦截器

feign.RequestInterceptor 接口的实现类

拦截器无法定义顺序

No guarantees are give with regards to the order that interceptors are applied.

Contract

Contract
处理Feign接口注解, 默认为 SpringMvcContract ,处理Spring mvc 注解,也就是我们为什么可以用 Spring mvc 注解的原因。


FeignClientsConfiguration 源码

org.springframework.cloud.netflix.feign.FeignClientsConfiguration 源码:

package org.springframework.cloud.netflix.feign;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.cloud.netflix.feign.support.ResponseEntityDecoder;
import org.springframework.cloud.netflix.feign.support.SpringDecoder;
import org.springframework.cloud.netflix.feign.support.SpringEncoder;
import org.springframework.cloud.netflix.feign.support.SpringMvcContract;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;

import com.netflix.hystrix.HystrixCommand;

import feign.Contract;
import feign.Feign;
import feign.Logger;
import feign.Retryer;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.hystrix.HystrixFeign;

@Configuration
public class FeignClientsConfiguration {

    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    @Autowired(required = false)
    private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();

    @Autowired(required = false)
    private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();

    @Autowired(required = false)
    private Logger logger;

    @Bean
    @ConditionalOnMissingBean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
    }

    @Bean
    @ConditionalOnMissingBean
    public Encoder feignEncoder() {
        return new SpringEncoder(this.messageConverters);
    }

    @Bean
    @ConditionalOnMissingBean
    public Contract feignContract(ConversionService feignConversionService) {
        return new SpringMvcContract(this.parameterProcessors, feignConversionService);
    }

    @Bean
    public FormattingConversionService feignConversionService() {
        FormattingConversionService conversionService = new DefaultFormattingConversionService();
        for (FeignFormatterRegistrar feignFormatterRegistrar : feignFormatterRegistrars) {
            feignFormatterRegistrar.registerFormatters(conversionService);
        }
        return conversionService;
    }

    @Configuration
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
        public Feign.Builder feignHystrixBuilder() {
            return HystrixFeign.builder();
        }
    }

    @Bean
    @ConditionalOnMissingBean
    public Retryer feignRetryer() {
        return Retryer.NEVER_RETRY;
    }

    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public Feign.Builder feignBuilder(Retryer retryer) {
        return Feign.builder().retryer(retryer);
    }

    @Bean
    @ConditionalOnMissingBean(FeignLoggerFactory.class)
    public FeignLoggerFactory feignLoggerFactory() {
        return new DefaultFeignLoggerFactory(logger);
    }
}

Spring Cloud中如何优雅的使用Feign调用接口
https://segmentfault.com/a/1190000012496398

OpenFeign/feign
https://github.com/OpenFeign/feign


上一篇 阿里巴巴Java开发手册

下一篇 Apache-Commons-IO使用笔记

阅读
评论
3,072
阅读预计14分钟
创建日期 2019-03-30
修改日期 2020-04-03
类别

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论