文章目录

一、OpenFeign简介

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

Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。

Feign可以与Eureka和Ribbon组合使用以支持负载均衡。

github:https://github.com/spring-cloud/spring-cloud-openfeign

官方文档:https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign

OpenFeign利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。

二、Springboot集成OpenFeign

1、引入starter

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

2、在启动类或者配置类上加@EnableFeignClients注解:

@SpringBootApplication
@EnableFeignClients
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3、声明Feign接口

@FeignClient("stores")
public interface StoreClient {
    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    List<Store> getStores();

    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    Page<Store> getStores(Pageable pageable);

    @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store);

    @RequestMapping(method = RequestMethod.DELETE, value = "/stores/{storeId:\\d+}")
    void delete(@PathVariable Long storeId);
}

@FeignClient的value值为客户端的名称(此时可以做到负载均衡),当然也可以写完整的主机名或者是ip端口值。

4、@EnableFeignClients属性解析

@EnableFeignClients用户开启Feign自动配置。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

	// basePackages的别名,允许更简洁的注释声明,例如:@ComponentScan("org.my.pkg")而不是@ComponentScan(basePackages="org.my.pkg")
	String[] value() default {};

	// 用户扫描Feign客户端的包,也就是@FeignClient标注的类,与value同义,并且互斥
	String[] basePackages() default {};

	// basePackages()的类型安全替代方案,用于指定要扫描带注释的组件的包。每个指定类别的包将被扫描。 考虑在每个包中创建一个特殊的无操作标记类或接口,除了被该属性引用之外没有其他用途。
	Class<?>[] basePackageClasses() default {};

	// 为所有假客户端定制@Configuration,默认配置都在FeignClientsConfiguration中,可以自己定制
	Class<?>[] defaultConfiguration() default {};

	// 可以指定@FeignClient标注的类,如果不为空,就会禁用类路径扫描
	Class<?>[] clients() default {};

}

5、@FeignClient属性解析

@FeignClient用于标注Feign客户端。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FeignClient {

	// name和value属性用于标注客户端名称,也可以用${propertyKey}获取配置属性
	@AliasFor("name")
	String value() default "";

	// 该类的Bean名称
	String contextId() default "";

	// name和value属性用于标注客户端名称,也可以用${propertyKey}获取配置属性
	@AliasFor("value")
	String name() default "";

	// 弃用 被qualifiers()替代。
	@Deprecated
	String qualifier() default "";

	// 模拟客户端的@Qualifiers值。如果qualifier()和qualifiers()都存在,我们将使用后者,除非qualifier()返回的数组为空或只包含空值或空白值,在这种情况下,我们将首先退回到qualifier(),如果也不存在,则使用default = contextId + "FeignClient"。
	String[] qualifiers() default {};

	// 绝对URL或可解析主机名
	String url() default "";

	// 是否应该解码404而不是抛出FeignExceptions
	boolean decode404() default false;

	// 用于模拟客户端的自定义配置类。可以包含组成客户端部分的覆盖@Bean定义,默认配置都在FeignClientsConfiguration类中,可以指定FeignClientsConfiguration类中所有的配置
	Class<?>[] configuration() default {};

	// 指定失败回调类
	Class<?> fallback() default void.class;

	// 为指定的假客户端接口定义一个fallback工厂。fallback工厂必须生成fallback类的实例,这些实例实现了由FeignClient注释的接口。
	Class<?> fallbackFactory() default void.class;

	// 所有方法级映射使用的路径前缀
	String path() default "";

	// 是否将虚拟代理标记为主bean。默认为true。
	boolean primary() default true;
}

可以通过以下任何一种方式向Feign客户端提供URL:

举例代码实例描述
在@FeignClient注解中提供url属性@FeignClient(name=“testClient”, url=“http://localhost:8081”)没有负载均衡能力,只能定向发送
在@FeignClient注解中和配置文件中同时使用url属性@FeignClient(name=“testClient”, url=“http://localhost:8081”)
在application.yml 配置spring.cloud.openfeign.client.config.testClient.url=http://localhost:8081
没有负载均衡能力,只能定向发送,优先使用注解中的url
在@FeignClient注解中配置name、在配置文件中配置url@FeignClient(name=“testClient”)
配置文件:spring.cloud.openfeign.client.config.testClient.url=http://localhost:8081
优先使用配置文件中的url,没有负载均衡能力
在@FeignClient注解中配置name@FeignClient(name=“testClient”)URL是通过负载平衡从注解的name属性中解析的。有负载均衡能力

三、覆盖默认配置

1、覆盖默认配置

在FeignClientsConfiguration类中,OpenFeign为我们做了很多默认配置,其中所有的配置我们都可以自定义并且覆盖。

@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
    //..
}

在指定了我们自定义的FooConfiguration配置类之后,FooConfiguration配置类中自定义的配置会覆盖FeignClientsConfiguration中的配置。

注意!FooConfiguration类并不需要@Configuration注释,如果加上了@Configuration,就会全局生效。如果只在@FeignClient中指定,那么就会只在该@FeignClient标注的类中生效。

注意!@FeignClient4.0.2以版本前,使用url属性时,不需要name属性。现在name属性是必需的。

// name属性和url属性支持表达式
@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
    //..
}

2、配置列表

Spring Cloud OpenFeign默认为Feign提供了以下bean配置:

  • Decoder feign解码器: 是一个ResponseEntityDecoder (被包装成了SpringDecoder)
  • Encoder feign编码器: 是一个SpringEncoder
  • Logger feign的Logger: 是一个Slf4jLogger
  • MicrometerObservationCapability micrometerObservationCapability: 如果feign-micrometer在类路径中并且ObservationRegistry可用
  • MicrometerCapability micrometerCapability: 如果feign-micrometer在类路径中,则MeterRegistry可用,而ObservationRegistry不可用
  • CachingCapability cachingCapability:如果使用了@EnableCaching批注会使用。可以通过spring.cloud.openfeign.cache.enabled配置禁用。
  • Contract feignContract: 是一个SpringMvcContract
  • Feign.Builder feignBuilder: 是一个FeignCircuitBreaker.Builder
  • Client feignClient: 如果Spring Cloud LoadBalancer在类路径上,则使用FeignBlockingLoadBalancerClient。如果它们都不在类路径中,则使用默认的feign客户端。

Spring Cloud OpenFeign没有为Feign默认提供以下bean,但仍然从应用程序上下文中查找这些类型的bean来创建feign客户端:

  • Logger.Level
  • Retryer
  • ErrorDecoder
  • Request.Options
  • Collection<RequestInterceptor>
  • SetterFactory
  • QueryMapEncoder
  • Capability (MicrometerObservationCapability and CachingCapability are provided by default)

其中Retryer 默认是Retryer.NEVER_RETRY,这将禁止重试。请注意,这种重试行为不同于openfeign默认行为,它将自动重试IOExceptions,将它们视为暂时的网络相关异常,以及从ErrorDecoder抛出的任何RetryableException。

我们可以自定义以上任意一个Bean,来覆盖默认的配置:

@Configuration
public class FooConfiguration {
    @Bean
    public Contract feignContract() {
        return new feign.Contract.Default();
    }

    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("user", "password");
    }
}

3、使用配置文件进行配置

@FeignClient的配置也可以在配置文件中进行配置,其中feignName就是@FeignClient的value值、name值和contextId值

spring:
    cloud:
        openfeign:
            client:
                config:
                    feignName:
                        url: http://remote-service.com
                        connectTimeout: 5000
                        readTimeout: 5000
                        loggerLevel: full
                        errorDecoder: com.example.SimpleErrorDecoder
                        retryer: com.example.SimpleRetryer
                        defaultQueryParameters:
                            query: queryValue
                        defaultRequestHeaders:
                            header: headerValue
                        requestInterceptors:
                            - com.example.FooRequestInterceptor
                            - com.example.BarRequestInterceptor
                        responseInterceptor: com.example.BazResponseInterceptor
                        dismiss404: false
                        encoder: com.example.SimpleEncoder
                        decoder: com.example.SimpleDecoder
                        contract: com.example.SimpleContract
                        capabilities:
                            - com.example.FooCapability
                            - com.example.BarCapability
                        queryMapEncoder: com.example.SimpleQueryMapEncoder
                        micrometer.enabled: false

可以配置全局的配置,并且配置文件优先:

spring:
    cloud:
        openfeign:
            client:
                config:
                    default:
                        connectTimeout: 5000
                        readTimeout: 5000
                        loggerLevel: basic

4、创建多个相同名称客户端

如果我们想要创建多个具有相同名称或url的feign客户端,以便它们指向相同的服务器,但是每个客户端都具有不同的自定义配置,那么我们必须使用@FeignClient的contextId属性,以避免这些配置beans的名称冲突。

@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)
public interface FooClient {
    //..
}

@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class)
public interface BarClient {
    //..
}

5、配置FeignClient不从父上下文继承beans

@Configuration
public class CustomConfiguration{

@Bean
public FeignClientConfigurer feignClientConfigurer() {
            return new FeignClientConfigurer() {

                @Override
                public boolean inheritParentConfiguration() {
                    return false;
                }
            };

        }
}

6、SpringEncoder 的配置

在我们提供的SpringEncoder中,我们为二进制内容类型设置空字符集,为所有其他内容类型设置UTF-8。
您可以通过将spring.cloud.openfeign.encoder.charset-from-content-type的值设置为true来修改此行为,以从Content-Type头字符集派生字符集。

7、Feign拦截器的配置及使用

拦截器是OpenFeign可用的一种强大的工具,它可以被用来在请求和响应前后进行一些额外的处理。要使用OpenFeign拦截器,可以通过以下步骤进行配置:

public class MyInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        // 在这里添加额外的处理逻辑,添加请求头
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes instanceof ServletRequestAttributes) {
            ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
            HttpServletRequest request = attributes.getRequest();
            String value = request.getHeader(headerName);
            template.header(headerName, headerValue);
        }
    }
}

将拦截器注册到OpenFeign:

@Configuration
public class MyFeignConfiguration {
 
    @Bean
    public MyInterceptor myInterceptor() {
        return new MyInterceptor();
    }
 
    // 非必须
    @Bean
    public Feign.Builder feignBuilder() {
        return Feign.builder()
                .requestInterceptor(myInterceptor());
    }
}

8、OpenFeign超时时间设置

(1)使用配置文件配置

在应用程序的配置文件(application.yml或application.properties)中,可以使用以下属性设置超时时间:

# YAML
feign:
  client:
    config:
      default:
        connectTimeout: 5000  # 连接超时时间
        readTimeout: 10000    # 读取超时时间

# Properties
feign.client.config.default.connectTimeout=5000  # 连接超时时间
feign.client.config.default.readTimeout=10000    # 读取超时时间

上述代码中,我们使用feign.client.config.default属性来配置默认的超时时间。connectTimeout属性设置连接超时时间,readTimeout属性设置读取超时时间。单位是毫秒。

(2)通过Java代码设置超时时间

补充说明: Feign的底层用的是Ribbon,但超时时间以Feign配置为准。
如果你更喜欢使用Java代码来配置openfeign,可以通过以下方式设置超时时间:

import feign.Request;

// 创建一个Request.Options对象来设置超时时间
Request.Options options = new Request.Options(connectTimeoutMillis, readTimeoutMillis);

// 在创建Feign客户端时指定Options对象
MyApi myApi = Feign.builder()
        .options(options)
        .target(MyApi.class, "https://example.com");

在上述代码中,我们创建了一个Request.Options对象,该对象包含连接超时时间和读取超时时间。然后将Options对象传递给Feign客户端。

(3)使用@FeignClient设置超时时间

使用@FeignClient注解的configuration属性来指定配置类。

首先,创建一个配置类,继承自feign.Request.Options类,并重写connectTimeoutMillis和readTimeoutMillis方法,以设置超时时间。

import feign.Request;
public class MyApiConfiguration extends Request.Options {
    public MyApiConfiguration(int connectTimeoutMillis, int readTimeoutMillis) {
        super(connectTimeoutMillis, readTimeoutMillis);
    }

    @Override
    public Integer connectTimeoutMillis() {
        return 5000;  // 设置连接超时时间为5秒
    }

    @Override
    public Integer readTimeoutMillis() {
        return 10000; // 设置读取超时时间为10秒
    }
}

然后,在使用@FeignClient注解进行声明时,使用configuration属性指定该配置类。

@FeignClient(name = "my-service", configuration = MyApiConfiguration.class)
public interface MyApi {
    // 接口定义
}

这样,只有针对MyApi接口的请求会使用这个配置类中的超时时间,级别更加细致。当然,你也可以在上述配置类中加入其它一些针对MyApi接口的配置,比如重试次数等等。

(4)使用拦截器设置超时时间

要为单独请求设置超时时间,可以通过实现RequestInterceptor接口,并在其中为请求添加超时时间信息。具体方法如下:

import feign.RequestInterceptor;
import feign.RequestTemplate;
public class TimeoutRequestInterceptor implements RequestInterceptor {
    private final int connectTimeoutMillis;
    private final int readTimeoutMillis;

    public TimeoutRequestInterceptor(int connectTimeoutMillis, int readTimeoutMillis) {
        this.connectTimeoutMillis = connectTimeoutMillis;
        this.readTimeoutMillis = readTimeoutMillis;
    }

    @Override
    public void apply(RequestTemplate template) {
        template.options(new Request.Options(connectTimeoutMillis, readTimeoutMillis));
    }
}

在上述代码中,我们创建了一个TimeoutRequestInterceptor类,实现了RequestInterceptor接口,并重写了其中的apply方法。在该方法中,将请求的超时时间信息添加到请求模板中。

然后,在实际使用Feign客户端时,创建该拦截器对象并加入到Feign客户端的拦截器链中。

例如,我们想要对一个名为MyApi的Feign客户端接口的某个请求设置超时时间,可以这样:

MyApi myApi = Feign.builder()
    .requestInterceptor(new TimeoutRequestInterceptor(3000, 5000)) // 为该客户端指定一个拦截器
    .target(MyApi.class, "https://example.com");

在上述代码中,我们创建了一个TimeoutRequestInterceptor对象,并使用requestInterceptor方法将其加入到Feign客户端的拦截器链中。这样,在名为MyApi的Feign客户端中发出的所有请求都会使用该超时时间。

如果只想为某些请求设置超时时间,而不是所有请求,可以在该拦截器中添加一些判断逻辑,根据请求的条件来判断是否要添加超时时间信息。

(5)使用@Headers设置超时时间

通过在接口方法上加上@Headers注解,将超时时间信息直接加在请求头中,从而实现为单独请求设置超时时间。

例如,我们想要针对MyApi接口的someMethod方法单独设置超时时间,可以这样:

@Headers({"connect-timeout:5000", "read-timeout:10000"})
@GET("/someMethod")
String someMethod();

在上述代码中,我们在@Headers注解中添加了connect-timeout和read-timeout两个请求头信息,用于设置连接超时时间和读取超时时间。这样,在调用someMethod方法时,会使用这些请求头信息中指定的超时时间设置。

需要注意的是,这种方法需要在每个接口方法上都进行设置,因此比较麻烦。但它的优点是灵活性比较高,可以为不同的接口方法设置不同的超时时间。同时,也可以在其他注解中添加相应的超时信息,如@PostMapping、@PutMapping等。

(6)为单独接口设置超时时间

在feign接口里加入Request.Options这个参数就可以单独为接口单独设置超时时间了

@PostMapping("test/")
ResponseVO<?> test(Request.Options options, @RequestBody TestRequestEntity entity);

调用的时候new 一下Options对象

 ResponseVO<?> resp = client.test(
        new Request.Options(70, TimeUnit.SECONDS, 70, TimeUnit.SECONDS, true),
        entity);

9、OpenFeign设置重试次数

(1)一般写法

定义一个继承自 Retryer 接口的类:

public class CustomRetryer implements Retryer {

    private final int maxAttempts;
    private final long backoff;
    int attempt;

    public CustomRetryer() {
        this(5, 1000);
    }

    public CustomRetryer(int maxAttempts, long backoff) {
        this.maxAttempts = maxAttempts;
        this.backoff = backoff;
        this.attempt = 1;
    }

    @Override
    public void continueOrPropagate(RetryableException e) {
        if (attempt++ >= maxAttempts) {
            throw e;
        }
        try {
            Thread.sleep(backoff);
        } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
            throw e;
        }
    }

    @Override
    public Retryer clone() {
        return new CustomRetryer();
    }
}

在 FeignClient 中使用上一步定义的重试器:

@FeignClient(name = "demo", url = "${demo.base-url}", configuration = CustomRetryer.class)
public interface DemoFeignClient {
    //...
}

在这个例子中,使用的是自定义的重试器 CustomRetryer,它重试 5 次,在每次重试之间休眠 1000 毫秒。如果重试次数超限,则抛出 RetryableException 异常。

(2)简单写法

除了使用自定义的 Retryer 之外,OpenFeign 还提供了另外一种设置重试次数的方式,那就是通过 Feign 的配置项进行设置。具体操作如下:

在 FeignClient 中引入 Feign 的默认配置:

@FeignClient(name = "demo", url = "${demo.base-url}", configuration = FeignConfiguration.class)
public interface DemoFeignClient {
    //...
}

自定义 FeignConfiguration 类:

@Configuration
public class FeignConfiguration {

    @Bean
    public Retryer retryer() {
        return new Retryer.Default(500, 5000, 3);
    }
}

在这里,我们使用 Retryer.Default 类生成一个默认的重试器,它会在当前请求失败后重试 3 次,并会在第一次重试前等待 500 毫秒,在第二次重试前等待 1000 毫秒,在第三次重试前等待 2000 毫秒,以此类推。

通过这两个步骤,我们就可以为每个 FeignClient 设置默认的重试次数了。

(3)为每个请求设置重试次数

如果我们需要为特定的请求设置不同的重试策略,则可以在对应的方法上加上 @Retryable 注解,并指定对应的 Retryer 类型,如下所示:

@FeignClient(name = "demo", url = "${demo.base-url}", configuration = FeignConfiguration.class)
public interface DemoFeignClient {

    @RequestMapping(method = RequestMethod.GET, value = "/get")
    @Retryable(maxAttempts = 2, value = { SomeRetryer.class })
    String getDemo();

}

在这个例子中,我们使用了自定义的重试器 SomeRetryer,并指定了最大重试次数为 2。注意,为了使用 @Retryable 注解,我们需要引入 Spring Retry 库的依赖:

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.2.5.RELEASE</version>
</dependency>

使用上述方式,我们可以为每个请求设置不同的重试策略,从而更加灵活地处理重试问题。

10、(扩展)Feign请求日志级别设置

Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节。默认显示的是DEBUG级别日志。

// 设置指定客户端的日志
logging.level.project.user.UserClient: DEBUG

就是对Feign接口的调用情况进行监控和输出。

总共有以下日志级别:
NONE:默认的,不显示任何日志。
BASIC:仅记录请求方法和URL以及响应状态代码和执行时间。
HEADERS:除了BASIC中定义的信息之外,还有请求和响应头的信息。
FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。

// 代码设置日志级别
@Configuration
public class FooConfiguration {
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

也可以局部配置,让调用的微服务生效,在@FeignClient 注解中指定使用的配置类:
在这里插入图片描述
也可以在yml中配置:
在yml配置文件中配置 Client 的日志级别才能正常输出日志,格式是"logging.level.feign接口包路径=debug"

logging:
  level:
   com.test.feign: debug # 包路径

补充:局部配置可以在yml中配置:
对应属性配置类:
org.springframework.cloud.openfeign.FeignClientProperties.FeignClientConfiguration

feign:
  client:
    config:
      mall‐order: #对应微服务
        loggerLevel: FULL

11、(扩展补充)契约配置Contract

Spring Cloud 在 Feign 的基础上做了扩展,可以让 Feign 支持 Spring MVC 的注解来调用。原生的 Feign 是不支持 Spring MVC 注解的,如果你想在 Spring Cloud 中使用原生的注解方式来定义客户端也是可以的,通过配置契约来改变这个配置,Spring Cloud 中默认的是 SpringMvcContract。

1)修改契约配置,支持Feign原生的注解

/**
* 修改契约配置,支持Feign原生的注解
* @return
*/
@Bean
public Contract feignContract() {
	return new Contract.Default();
}

注意:修改契约配置后,OrderFeignService 不再支持springmvc的注解,需要使用Feign原生的注解

2)OrderFeignService 中配置使用Feign原生的注解

@FeignClient(value = "mall‐order",path = "/order")
public interface OrderFeignService {
@RequestLine("GET /findOrderByUserId/{userId}")
	public R findOrderByUserId(@Param("userId") Integer userId);
}

3)补充,也可以通过yml配置契约

feign:
client:
config:
mall‐order: #对应微服务
loggerLevel: FULL
contract: feign.Contract.Default #指定Feign原生注解契约配置

12、GZIP 压缩配置

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

feign:
	# 配置 GZIP 来压缩数据
	compression:
	request:
	enabled: true
	# 配置压缩的类型
	mime‐types: text/xml,application/xml,application/json
	# 最小压缩值
	min‐request‐size: 2048
	 response:
	 enabled: true

注意:只有当 Feign 的 Http Client 不是 okhttp3 的时候,压缩才会生效,配置源码在FeignAcceptGzipEncodingAutoConfiguration
在这里插入图片描述
在这里插入图片描述
核心代码就是 @ConditionalOnMissingBean(type=“okhttp3.OkHttpClient”),表示Spring BeanFactory 中不包含指定的 bean 时条件匹配,也就是没有启用 okhttp3 时才会进行压缩配置。

13、编码器解码器配置

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

扩展点:Encoder & Decoder

Java配置方式
配置编码解码器只需要在 Feign 的配置类中注册 Decoder 和 Encoder 这两个类即可:

@Bean
public Decoder decoder() {
	return new JacksonDecoder();
}
@Bean
public Encoder encoder() {
	return new JacksonEncoder();
}

yml配置方式

feign:
	client:
	config:
	mall‐order: #对应微服务
	# 配置编解码器
	encoder: feign.jackson.JacksonEncoder
	decoder: feign.jackson.JacksonDecoder

四、超时处理

我们可以在默认客户端和指定客户端上配置超时。OpenFeign使用两个超时参数:
connectTimeout:连接超时时间。
readTimeout:从建立连接开始到返回响应的时间。

五、手动创建feign客户端

可以使用Feign Builder API创建客户端来进行定制。

// 手动创建两个Feign客户端并配置其拦截器和name属性,FeignClientsConfiguration.class仍然是它们的默认配置
@Import(FeignClientsConfiguration.class)
class FooController {

    private FooClient fooClient;

    private FooClient adminClient;

    @Autowired
    public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerObservationCapability micrometerObservationCapability) {
        this.fooClient = Feign.builder().client(client)
                .encoder(encoder)
                .decoder(decoder)
                .contract(contract)
                .addCapability(micrometerObservationCapability)
                .requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
                .target(FooClient.class, "https://PROD-SVC");

        this.adminClient = Feign.builder().client(client)
                .encoder(encoder)
                .decoder(decoder)
                .contract(contract)
                .addCapability(micrometerObservationCapability)
                .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
                .target(FooClient.class, "https://PROD-SVC");
    }
}

还可以使用Builder 来配置FeignClient不从父上下文继承beans。可以通过在生成器上重写调用“inheritParentContext(false)”来实现这一点。

六、Feign的SpringCloud断路器

如果Spring Cloud CircuitBreaker在classpath,并且spring.cloud.openfeign.circuitbreaker.enabled=true,Feign将使用断路器包装所有方法。

要在每个客户端的基础上禁用Spring Cloud CircuitBreaker支持,请创建一个普通的Feign.Builder。具有“prototype”范围的构建器,例如:

@Configuration
public class FooConfiguration {
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    }
}
// 通过提供CircuitBreakerNameResolver的bean,可以更改断路器名称模式。
@Configuration
public class FooConfiguration {
    @Bean
    public CircuitBreakerNameResolver circuitBreakerNameResolver() {
        return (String feignClientName, Target<?> target, Method method) -> feignClientName + "_" + method.getName();
    }
}

要启用Spring Cloud CircuitBreaker组,请将spring.cloud.openfeign.circuitbreaker.group.enabled属性设置为true(默认为false)。

1、使用配置属性配置断路器

假如说有一个Feign客户端:

@FeignClient(url = "http://localhost:8080")
public interface DemoClient {

    @GetMapping("demo")
    String getDemo();
}

可以通过执行以下操作,使用配置属性对其进行配置:

spring:
  cloud:
    openfeign
      circuitbreaker:
        enabled: true
        alphanumeric-ids:
          enabled: true
resilience4j:
  circuitbreaker:
    instances:
      DemoClientgetDemo:
        minimumNumberOfCalls: 69
  timelimiter:
    instances:
      DemoClientgetDemo:
        timeoutDuration: 10s

2、fallback

Spring Cloud CircuitBreaker支持fallback的概念:当电路断开或出现错误时执行的默认代码路径。要为给定的@FeignClient启用回退,请将fallback属性设置为实现回退的类名。并且还需要将其实现声明为Spring bean。

@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
protected interface TestClient {

    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello getHello();

    @RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
    String getException();

}

@Component
static class Fallback implements TestClient {

    @Override
    public Hello getHello() {
        throw new NoFallbackAvailableException("Boom!", new RuntimeException());
    }

    @Override
    public String getException() {
        return "Fixed response";
    }

}

如果需要访问触发fallback触发器的原因,可以使用@FeignClient中的fallbackFactory属性。

@FeignClient(name = "testClientWithFactory", url = "http://localhost:${server.port}/",
            fallbackFactory = TestFallbackFactory.class)
protected interface TestClientWithFactory {

    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello getHello();

    @RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
    String getException();

}

@Component
static class TestFallbackFactory implements FallbackFactory<FallbackWithFactory> {

    @Override
    public FallbackWithFactory create(Throwable cause) {
        return new FallbackWithFactory();
    }

}

static class FallbackWithFactory implements TestClientWithFactory {

    @Override
    public Hello getHello() {
        throw new NoFallbackAvailableException("Boom!", new RuntimeException());
    }

    @Override
    public String getException() {
        return "Fixed response";
    }

}

3、Feign客户端的primary属性

当使用Feign和Spring Cloud 断路器 fallback时,在ApplicationContext中有多个相同类型的beans。这将导致@Autowired不起作用,因为没有确切的一个bean,或者一个被标记为主bean。为了解决这个问题,Spring Cloud OpenFeign将所有的Feign实例标记为@Primary,因此Spring Framework将知道要注入哪个bean。在某些情况下,这可能并不理想。要关闭此行为,请将@FeignClient的primary属性设置为false(默认为true)。

@FeignClient(name = "hello", primary = false)
public interface HelloClient {
    // methods here
}


七、Feign的继承重用

Feign通过单一继承接口支持样板API。这允许将常见操作分组到方便的基本接口中。

// 共用接口实例
public interface UserService {

    @RequestMapping(method = RequestMethod.GET, value ="/users/{id}")
    User getUser(@PathVariable("id") long id);
}

@RestController
public class UserResource implements UserService {

}

@FeignClient("users")
public interface UserClient extends UserService {
}

但是!@FeignClient接口不应在服务器和客户端之间共享,并且不再支持在类级别上使用@RequestMapping注释@FeignClient接口。

八、Feign请求响应的压缩

可以考虑为您的feign请求启用请求或响应GZIP压缩。您可以通过启用以下属性之一来实现这一点:

spring.cloud.openfeign.compression.request.enabled=true
spring.cloud.openfeign.compression.response.enabled=true

Feign请求压缩为您提供了类似于您可能为web服务器设置的设置:

spring.cloud.openfeign.compression.request.enabled=true
spring.cloud.openfeign.compression.request.mime-types=text/xml,application/xml,application/json
spring.cloud.openfeign.compression.request.min-request-size=2048

以上这些属性允许您选择压缩的媒体类型和最小请求阈值长度。

注意!由于OkHttpClient使用“透明”压缩,如果存在content-encoding或accept-encoding头,则该压缩将被禁用,因此当feign.okhttp.OkHttpClient存在于classpath中并且spring.cloud.openfeign.okhttp.enabled设置为true时,我们不启用压缩。

九、开启Feign的缓存

如果使用了@EnableCaching注释,将创建并注册一个CachingCapability bean,以便您的Feign客户端能够识别其接口上的@Cache*注解:

public interface DemoClient {

    @GetMapping("/demo/{filterParam}")
    @Cacheable(cacheNames = "demo-cache", key = "#keyParam")
    String demoEndpoint(String keyParam, @PathVariable String filterParam);
}

还可以通过属性spring.cloud.openfeign.cache.enabled=false禁用该功能。

十、@SpringQueryMap注解支持

Spring Cloud OpenFeign提供了一个等价的@SpringQueryMap注释,用于将POJO或Map参数注释为查询参数Map。

例如,Params类定义了参数param1和param2:

// Params.java
public class Params {
    private String param1;
    private String param2;

    // [Getters and setters omitted for brevity]
}

下面的feign客户端通过使用@SpringQueryMap注解来使用Params类:

@FeignClient("demo")
public interface DemoTemplate {

    @GetMapping(path = "/demo")
    String demoEndpoint(@SpringQueryMap Params params);
}

如果您需要对生成的查询参数映射进行更多的控制,您可以实现一个自定义的QueryMapEncoder bean。

十一、FeignClient的参数传递给服务提供方的方式

1、path路径上携带参数

    /**
     * 服务提供方:path路径上携带参数
     */
    @GetMapping("/test1/{myId}")
    public String test1(@PathVariable String myId) {
        System.out.println("LiveRoomController.test1");
        System.out.println(myId);
        return "success";
    }
    
    /**
     * FeignClient:path路径上携带参数
     */
    @GetMapping("/test1/{myId}")
    String test1(@PathVariable("myId") String myId);

2、单个简单数据类型

    /**
     * 服务提供方:单个简单数据类型
     */
    @GetMapping("/test2")
    public String test2(String test2Str) {
        System.out.println("LiveRoomController.test2");
        System.out.println(test2Str);
        return "success";
    }

    /**
     * FeignClient:单个简单数据类型
     */
    @GetMapping("/test2")
    String test2(@RequestParam("test2Str") String test2Str);

3、多个简单数据类型

    /**
     * 服务提供方:多个简单数据类型
     */
    @GetMapping("/test3")
    public String test3(String test3Str1, String test3Str2) {
        System.out.println("LiveRoomController.test3");
        System.out.println(test3Str1 + "||" + test3Str2);
        return "success";
    }

    /**
     * FeignClient:多个简单数据类型
     */
    @GetMapping("/test3")
    String test3(@RequestParam("test3Str1") String test3Str1,@RequestParam("test3Str2")  String test3Str2);

4、Path + 多个简单数据类型

    /**
     * 服务提供方:Path  + 多个简单数据类型
     */
    @GetMapping("/test4/{myId}")
    public String test4(@PathVariable String myId, String test4Str1, String test4Str2) {
        System.out.println("LiveRoomController.test4");
        System.out.println(myId + "||" + test4Str1 + "||" + test4Str2);
        return "success";
    }

    /**
     * FeignClient:Path  + 多个简单数据类型
     */
    @GetMapping("/test4/{myId}")
    String test4(@PathVariable("myId") String myId, @RequestParam("test4Str1") String test4Str1, @RequestParam("test4Str2") String test4Str2);

5、JavaBean对象

    /**
     * 服务提供方:JavaBean对象
     */
    @GetMapping("/test5/{myId}")
    public String test5(@PathVariable String myId, Student student) {
        System.out.println("LiveRoomController.test5");
        System.out.println(myId + "||" + student);
        return "success";
    }

    /**
     * FeignClient:JavaBean对象、Map
     */
    @GetMapping("/test5/{myId}")
    String test5(@PathVariable("myId") String myId, Student student);

6、多path路径上携带参数

    /**
     * 服务提供方:多path路径上携带参数
     */
    @GetMapping("/test6/{myId}/test66/{myId2}")
    public String test6(@PathVariable("myId") String myId, @PathVariable("myId2") String myId2) {
        System.out.println("LiveRoomController.test6");
        System.out.println(myId + "||" + myId2);
        return "success";
    }

    /**
     * FeignClient:多path路径上携带参数
     */
    @GetMapping("/test6/{myId}/test66/{myId2}")
    String test6(@PathVariable("myId") String myId, @PathVariable("myId2") String myId2);

7、post获取请求体

    /**
     * 服务提供方:post获取请求体
     */
    @PostMapping("/test7/{myId}")
    public String test7(@PathVariable("myId") String myId, @RequestBody Student student){
        System.out.println("LiveRoomController.test7");
        System.out.println(myId + "||" + student);
        return "success";
    }

    /**
     * FeignClient:post获取请求体
     */
    @PostMapping("/test7/{myId}")
    String test7(@PathVariable("myId") String myId, @RequestBody Student student);

8、测试一下吧

System.out.println(commonSurface.test1("this is test1"));
System.out.println("-------------");

System.out.println(commonSurface.test2("this is test2"));
System.out.println("-------------");

System.out.println(commonSurface.test3("this is test3", "this is test3-2"));
System.out.println("-------------");

System.out.println(commonSurface.test4("this is myId", "this is test4", "this is test4-2"));
System.out.println("-------------");

Student s = new Student();
s.setId(1);
s.setName("张三");
System.out.println(commonSurface.test5("this is myId", s));
System.out.println("-------------");

System.out.println(commonSurface.test6("this is myId", "this is myId2"));
System.out.println("-------------");

System.out.println(commonSurface.test7("this is myId", s));
System.out.println("-------------");

十二、修改客户端组件

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

Feign发起调用真正执行逻辑:feign.Client#execute (扩展点)
在这里插入图片描述

1、配置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>

然后修改yml配置,将 Feign 的 Apache HttpClient启用 :

feign:
#feign 使用 Apache HttpClient 可以忽略,默认开启
  httpclient:
    enabled: true

关于配置可参考源码: org.springframework.cloud.openfeign.FeignAutoConfiguration
在这里插入图片描述
测试:调用会进入feign.httpclient.ApacheHttpClient#execute

2、引入OkHttp

引入依赖

<dependency>
	<groupId>io.github.openfeign</groupId>
	<artifactId>feign‐okhttp</artifactId>
</dependency>

然后修改yml配置,将 Feign 的 HttpClient 禁用,启用 OkHttp,配置如下:

feign:
	#feign 使用 okhttp
	httpclient:
		enabled: false
	okhttp:
		enabled: true

关于配置可参考源码: org.springframework.cloud.openfeign.FeignAutoConfiguration
在这里插入图片描述
测试:调用会进入feign.okhttp.OkHttpClient#execute

参考资料

https://www.cnblogs.com/64Byte/p/13293875.html

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐