0


Spring Cloud OpenFeign官方文档学习

文章目录

推荐

spring cloud openfeign官方文档介绍

SpringCloud-OpenFeign官方文档使用大全详解

SpringCloud OpenFeign 全功能配置详解(一文吃透OpenFeign)

如何处理Feign的重试问题

Spring Cloud OpenFeign 关键知识详解

OpenFeign 全方位讲解

Spring Cloud Open Feign专栏

关于Feign的重试机制

OpenFeign 的超时重试机制以及底层实现原理

如何处理Feign的重试问题

下面文档基本就是官方文档的翻译,源自:spring cloud openfeign官方文档介绍,做了一丢丢的补充

一、OpenFeign简介

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

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

Feign 是一个声明式的 Web Service 客户端。它使编写 Web Service 客户端更容易。

要使用 Feign,需要创建一个接口并对其进行注解。

它有可插拔的注解支持,包括 Feign 注解和 JAX-RS 注解。Feign 还支持可插拔的编码器和解码器。

Spring Cloud 增加了对 Spring MVC 注解的支持,并支持使用 Spring Web 中默认使用的 HttpMessageConverters。

Spring Cloud 集成了 Eureka、Spring Cloud CircuitBreaker以及Spring Cloud LoadBalancer,以便在使用Feign时提供一个负载均衡的http客户端。

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

二、Springboot集成OpenFeign

1、引入starter

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

在引入具体的版本之前,可以看下spring-cloud与springboot对应版本的兼容性
Release TrainSpring Boot Generation2023.0.x aka Leyton3.2.x2022.0.x aka Kilburn3.0.x, 3.1.x (Starting with 2022.0.3)2021.0.x aka Jubilee2.6.x, 2.7.x (Starting with 2021.0.3)2020.0.x aka Ilford2.4.x, 2.5.x (Starting with 2020.0.3)Hoxton2.2.x, 2.3.x (Starting with SR5)Greenwich2.1.xFinchley2.0.xEdgware1.5.xDalston1.5.x

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

@SpringBootApplication@EnableFeignClientspublicclassApplication{publicstaticvoidmain(String[] args){SpringApplication.run(Application.class, args);}}

3、声明Feign接口

@FeignClient("stores")publicinterfaceStoreClient{@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")Storeupdate(@PathVariable("storeId")Long storeId,Store store);@RequestMapping(method =RequestMethod.DELETE, value ="/stores/{storeId:\\d+}")voiddelete(@PathVariableLong storeId);}

@FeignClient注解用于创建1个feign客户端,它在容器中的bean的名称就是接口的全限定名(可以通过@FeignClient注解的qualifiers属性来修改);

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

可以通过@FeignClient的url属性来指定要访问的url(可以是全路径名,也可以是主机名);

上面例子中的feign客户端会去寻找stores服务对应的物理地址,如果你使用了Eureka作为注册中心,那么它就会从Eureka中服务列表中解析stores服务。如果你不想使用Eureka,你可以通过SimpleDiscoveryClient 配置stores服务列表。

4、@EnableFeignClients属性解析

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

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(FeignClientsRegistrar.class)public@interfaceEnableFeignClients{// 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@Inheritedpublic@interfaceFeignClient{// name和value属性用于标注客户端名称,也可以用${propertyKey}获取配置属性@AliasFor("name")Stringvalue()default"";// 该类的Bean名称StringcontextId()default"";// name和value属性用于标注客户端名称,也可以用${propertyKey}获取配置属性@AliasFor("value")Stringname()default"";// 弃用 被qualifiers()替代。@DeprecatedStringqualifier()default"";// 模拟客户端的@Qualifiers值。// 如果qualifier()和qualifiers()都存在,我们将使用后者,// 除非qualifier()返回的数组为空或只包含空值或空白值,// 在这种情况下,我们将首先退回到qualifier(),// 如果也不存在,则使用default = contextId + "FeignClient"。String[]qualifiers()default{};// 绝对URL或可解析主机名Stringurl()default"";// 是否应该解码404而不是抛出FeignExceptionsbooleandecode404()defaultfalse;// 用于模拟客户端的自定义配置类。可以包含组成客户端部分的覆盖@Bean定义,// 默认配置都在FeignClientsConfiguration类中,可以指定FeignClientsConfiguration类中所有的配置Class<?>[]configuration()default{};// 指定失败回调类Class<?>fallback()defaultvoid.class;// 为指定的假客户端接口定义一个fallback工厂。// fallback工厂必须生成fallback类的实例,这些实例实现了由FeignClient注释的接口。Class<?>fallbackFactory()defaultvoid.class;// 所有方法级映射使用的路径前缀Stringpath()default"";// 是否将虚拟代理标记为主bean。默认为true。booleanprimary()defaulttrue;}

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

三、覆盖默认配置

1、覆盖默认配置

在spring cloud feign中的1个核心概念就是命名客户端,每1个feign客户端都由各种组件,按照协议要求从远程服务器发起请求完成功能,每个这样的feign客户端都使用@FeignClient注解来标识。

spring cloud 会为每1个feign客户端使用FeignClientsConfiguration这个配置类创建1个spring容器,FeignClientsConfiguration类中定义的组件有:feign.Decoder、feign.Encoder、feign.Contract,并且可以使用@FeignClient注解的contextId属性来覆盖spring容器的名字。

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

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

在指定了我们自定义的FooConfiguration配置类之后,FooConfiguration配置类中自定义的配置会与FeignClientsConfiguration中的配置合并,并且FooConfiguration中的配置的组件的优先级会更高(覆盖FeignClientsConfiguration配置类中给我们的默认配置)。

注意!FooConfiguration类并不需要@Configuration注解,如果加上了@Configuration,就会全局生效,那么它里面定义的

feign.Decoder

,

feign.Encoder

,

feign.Contract

, etc.等组件就会成为默认配置(如果不想要FooConfiguration类中定义的组件成为默认组件,但是FooConfiguration上又加了@Configuration注解,那么就需要排除它,不让它被扫描到)。如果只在==@FeignClient中指定,那么就会只在该@FeignClient标注的类中生效==。@EnableFeignClients注解也可以指定配置类,它会在由该注解扫描到的客户端中应用指定配置类中定义的组件。

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

// name属性和url属性支持占位符表达式@FeignClient(name ="${feign.name}", url ="${feign.url}")publicinterfaceStoreClient{//..}

2、配置列表

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

  • Decoder feign解码器: 是一个ResponseEntityDecoder (被包装成了SpringDecoder)
  • Encoder feign编码器: 是一个SpringEncoder
  • Logger feign的Logger: 是一个Slf4jLogger
  • MicrometerObservationCapability micrometerObservationCapability: 如果feign-micrometer在类路径中并且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-starter-openfeign支持spring-cloud-starter-loadbalancer,但是因为后者是个可选依赖,如果想使用这个依赖的话,那么就需要自己引入它。

可以通过设置feign.okhttp.enabled、feign.httpclient.enabled、feign.httpclient.hc5.enabled为true,来分别开启对应的OkHttpClient、ApacheHttpClient 、ApacheHC5 客户端,并且要把它们的依赖放在类路径上。或者在容器中定义1个org.apache.http.impl.client.CloseableHttpClient、或者okhttp3.OkHttpClient、或者org.apache.hc.client5.http.impl.classic.CloseableHttpClient的客户端bean来切换不同的客户端实现。

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,来覆盖默认的配置:

@ConfigurationpublicclassFooConfiguration{@BeanpublicContractfeignContract(){returnnewfeign.Contract.Default();}@BeanpublicBasicAuthRequestInterceptorbasicAuthRequestInterceptor(){returnnewBasicAuthRequestInterceptor("user","password");}}

这个配置会使用feign.Contract.Default替换默认的SpringMvcContract,并且会将定义的BasicAuthRequestInterceptor这个bean添加到RequestInterceptor集合当中去。

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

@FeignClient的配置也可以在配置文件中进行配置,其中feignName就是@FeignClient的value值、name值和contextId值,同时,在使用负载均衡时,这里的feignName也会被用来查询服务实例。

在如下配置中指定的类,必须在容器中有定义1个或者有1个默认的构造器。

feign:client:config:feignName:connectTimeout:5000readTimeout:5000loggerLevel: full
                errorDecoder: com.example.SimpleErrorDecoder
                retryer: com.example.SimpleRetryer
                defaultQueryParameters:query: queryValue
                defaultRequestHeaders:header: headerValue
                requestInterceptors:- com.example.FooRequestInterceptor
                    - com.example.BarRequestInterceptor
                decode404:falseencoder: com.example.SimpleEncoder
                decoder: com.example.SimpleDecoder
                contract: com.example.SimpleContract
                capabilities:- com.example.FooCapability
                    - com.example.BarCapability
                queryMapEncoder: com.example.SimpleQueryMapEncoder
                metrics.enabled:false

也可以通过@EnableFeignClient注解的defaultConfiguration属性根据上面类似的方式来指定1个配置类,区别在于这种方式将会应用到所有的feign客户端。

也可以通过设置名为default的feignName来作全局的配置,并且配置文件优先(相比于配置类的方式,但是如果你想更改这个优先级,可以把feign.client.default-to-properties设置为false):

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

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

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

@FeignClient(contextId ="fooClient", name ="stores", configuration =FooConfiguration.class)publicinterfaceFooClient{//..}@FeignClient(contextId ="barClient", name ="stores", configuration =BarConfiguration.class)publicinterfaceBarClient{//..}

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

可以通过配置1个FeignClientConfigurer的bean,并且重写这个bean的inheritParentConfiguration(),并且返回false,来配置feign客户端不从父容器中拿bean组件

@ConfigurationpublicclassCustomConfiguration{@BeanpublicFeignClientConfigurerfeignClientConfigurer(){returnnewFeignClientConfigurer(){@OverridepublicbooleaninheritParentConfiguration(){returnfalse;}};}}

提示:默认情况下,feign客户端不会对

/

编码,可以通过设置feign.client.decodeSlash为false来更改这个行为。

6、SpringEncoder 的配置

在我们提供的SpringEncoder中,我们为二进制内容类型设置空字符集,为所有其他内容类型设置UTF-8。

您可以通过将spring.cloud.openfeign.encoder.charset-from-content-type的值设置为true来修改此行为,以从Content-Type头字符集派生字符集。

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

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

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

将拦截器注册到OpenFeign:

@ConfigurationpublicclassMyFeignConfiguration{@BeanpublicMyInterceptormyInterceptor(){returnnewMyInterceptor();}// 非必须@BeanpublicFeign.BuilderfeignBuilder(){returnFeign.builder().requestInterceptor(myInterceptor());}}

8、OpenFeign超时时间设置

我们可以配置默认的超时时间,也可以为指定的feign客户端配置超时时间。

Open Feign提供了2个超时参数供设置:connectTimeout(防止由于服务器处理时间过长而阻塞调用者)、readTimeout(从连接建立开始到响应花费时间)

(1)使用配置文件配置

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

# YAMLfeign: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代码设置超时时间

如果你更喜欢使用Java代码来配置openfeign,可以通过以下方式设置超时时间:

importfeign.Request;// 创建一个Request.Options对象来设置超时时间Request.Options options =newRequest.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方法,以设置超时时间。

importfeign.Request;publicclassMyApiConfigurationextendsRequest.Options{publicMyApiConfiguration(int connectTimeoutMillis,int readTimeoutMillis){super(connectTimeoutMillis, readTimeoutMillis);}@OverridepublicIntegerconnectTimeoutMillis(){return5000;// 设置连接超时时间为5秒}@OverridepublicIntegerreadTimeoutMillis(){return10000;// 设置读取超时时间为10秒}}

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

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

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

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

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

importfeign.RequestInterceptor;importfeign.RequestTemplate;publicclassTimeoutRequestInterceptorimplementsRequestInterceptor{privatefinalint connectTimeoutMillis;privatefinalint readTimeoutMillis;publicTimeoutRequestInterceptor(int connectTimeoutMillis,int readTimeoutMillis){this.connectTimeoutMillis = connectTimeoutMillis;this.readTimeoutMillis = readTimeoutMillis;}@Overridepublicvoidapply(RequestTemplate template){
        template.options(newRequest.Options(connectTimeoutMillis, readTimeoutMillis));}}

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

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

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

MyApi myApi =Feign.builder().requestInterceptor(newTimeoutRequestInterceptor(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")StringsomeMethod();

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

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

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

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

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

调用的时候new 一下Options对象

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

9、OpenFeign设置重试次数

(1)一般写法

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

publicclassCustomRetryerimplementsRetryer{privatefinalint maxAttempts;privatefinallong backoff;int attempt;publicCustomRetryer(){this(5,1000);}publicCustomRetryer(int maxAttempts,long backoff){this.maxAttempts = maxAttempts;this.backoff = backoff;this.attempt =1;}@OverridepublicvoidcontinueOrPropagate(RetryableException e){if(attempt++>= maxAttempts){throw e;}try{Thread.sleep(backoff);}catch(InterruptedException ignored){Thread.currentThread().interrupt();throw e;}}@OverridepublicRetryerclone(){returnnewCustomRetryer();}}

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

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

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

(2)简单写法

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

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

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

自定义 FeignConfiguration 类:

@ConfigurationpublicclassFeignConfiguration{@BeanpublicRetryerretryer(){returnnewRetryer.Default(500,5000,3);}}

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

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

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

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

@FeignClient(name ="demo", url ="${demo.base-url}", configuration =FeignConfiguration.class)publicinterfaceDemoFeignClient{@RequestMapping(method =RequestMethod.GET, value ="/get")@Retryable(maxAttempts =2, value ={SomeRetryer.class})StringgetDemo();}

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

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

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

10、Feign请求日志级别设置

每1个feign客户端都会创建1个logger,默认情况下,logger的名字就是接口的全类名,feign日志只会对debug级别才打印出来。

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

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

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

总共有以下日志级别:

  • NONE:默认的,不显示任何日志。
  • BASIC:仅记录请求方法、URL、响应状态码、执行时间。
  • HEADERS:除了BASIC中定义的信息之外,还有请求和响应头。
  • FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。
// 代码设置日志级别(修改默认的日志级别)@ConfigurationpublicclassFooConfiguration{@BeanLogger.LevelfeignLoggerLevel(){returnLogger.Level.FULL;}}

四、手动创建feign客户端

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

// 手动创建两个Feign客户端并配置其拦截器和name属性,FeignClientsConfiguration.class仍然是它们的默认配置// FeignClientsConfiguration是Spring Cloud OpenFeign提供的默认配置类@Import(FeignClientsConfiguration.class)classFooController{privateFooClient fooClient;privateFooClient adminClient;@AutowiredpublicFooController(Client client,Encoder encoder,Decoder decoder,Contract contract,MicrometerObservationCapability micrometerObservationCapability){this.fooClient =Feign.builder().client(client).encoder(encoder).decoder(decoder)// Contract 定义了在接口上能够使用的注解, 这里自动注入的Contract支持springmvc注解, // 而不是feign的原始注解.contract(contract).addCapability(micrometerObservationCapability).requestInterceptor(newBasicAuthRequestInterceptor("user","user"))// PROD-SVC是请求的服务名.target(FooClient.class,"https://PROD-SVC");this.adminClient =Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract).addCapability(micrometerObservationCapability).requestInterceptor(newBasicAuthRequestInterceptor("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”范围的构建器,例如:

@ConfigurationpublicclassFooConfiguration{@Bean@Scope("prototype")publicFeign.BuilderfeignBuilder(){returnFeign.builder();}}

断路器的名字遵循这样的格式:<feign客户端类名>#<被调用的方法名>(<参数类型>)。比如当调用1个FooClient接口的bar方法,并且这个方法没有参数时,断路器的名字就是:FooClient#bar()

注意:从2020.0.2开始,circuit breaker 名称模式已经从

<feignClientName>_<calledMethod>

改变。使用2020.0.4中引入的 CircuitBreakerNameResolver,circuit breaker 名称可以保留旧模式。

通过提供CircuitBreakerNameResolver的bean,可以更改断路器名称模式,如下所示。

@ConfigurationpublicclassFooConfiguration{@BeanpublicCircuitBreakerNameResolvercircuitBreakerNameResolver(){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")publicinterfaceDemoClient{@GetMapping("demo")StringgetDemo();}

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

spring:cloud:
    openfeign
      circuitbreaker:enabled:truealphanumeric-ids:enabled:trueresilience4j:circuitbreaker:instances:DemoClientgetDemo:minimumNumberOfCalls:69timelimiter:instances:DemoClientgetDemo:timeoutDuration: 10s

如果你想切换回 Spring Cloud 2022.0.0 之前使用的 circuit breaker name,你可以将 spring.cloud.openfeign.circuitbreaker.alphanumeric-ids.enabled 设置为 false。

2、fallback

fallback降级处理

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

@FeignClient(name ="test", url ="http://localhost:${server.port}/", fallback =Fallback.class)protectedinterfaceTestClient{@RequestMapping(method =RequestMethod.GET, value ="/hello")HellogetHello();@RequestMapping(method =RequestMethod.GET, value ="/hellonotfound")StringgetException();}@ComponentstaticclassFallbackimplementsTestClient{@OverridepublicHellogetHello(){thrownewNoFallbackAvailableException("Boom!",newRuntimeException());}@OverridepublicStringgetException(){return"Fixed response";}}
fallbackFactory降级处理

如果有需要知道触发fallback的原因,可以使用@FeignClient中的fallbackFactory属性。

@FeignClient(name ="testClientWithFactory", 
             url ="http://localhost:${server.port}/",// 使用fallbackFactory属性指定TestFallbackFactory(它要实现FallbackFactory)
             fallbackFactory =TestFallbackFactory.class)protectedinterfaceTestClientWithFactory{@RequestMapping(method =RequestMethod.GET, value ="/hello")HellogetHello();@RequestMapping(method =RequestMethod.GET, value ="/hellonotfound")StringgetException();}@Component// 实现FallbackFactory接口, 在create(Throwable)方法种返回1个实现了feign接口的对象staticclassTestFallbackFactoryimplementsFallbackFactory<FallbackWithFactory>{@OverridepublicFallbackWithFactorycreate(Throwable cause){returnnewFallbackWithFactory();}}staticclassFallbackWithFactoryimplementsTestClientWithFactory{@OverridepublicHellogetHello(){thrownewNoFallbackAvailableException("Boom!",newRuntimeException());}@OverridepublicStringgetException(){return"Fixed response";}}

3、Feign客户端的primary属性

(要知道有这回事)

当使用Feign和Spring Cloud CircuitBreaker 降级功能时,在ApplicationContext中有多个相同类型的beans。这将导致@Autowired不起作用,因为没有确切的一个bean,或者一个被标记为@Primary注解的bean。

为了解决这个问题,Spring Cloud OpenFeign将所有的Feign实例都标记为了@Primary,因此Spring Framework将知道要注入哪个bean。在某些情况下,这可能并不理想。要关闭此行为,请将@FeignClient的primary属性设置为false(默认为true)。

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

六、Feign的继承重用

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

// 共用接口实例publicinterfaceUserService{@RequestMapping(method =RequestMethod.GET, value ="/users/{id}")UsergetUser(@PathVariable("id")long id);}// 提供(方)服务@RestControllerpublicclassUserResourceimplementsUserService{}// 调用(方)服务@FeignClient("users")publicinterfaceUserClientextendsUserService{}

注意:@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 Capability 的支持

Feign Capability 暴露了Feign的核心组件,因此这些组件可以被修改。例如,这些功能可以接受客户端,对其进行装饰,并将装饰后的实例反馈给 Feign。对 Micrometer 的支持就是一个很好的现实生活中的例子。参见 [micrometer-support]。

创建一个或多个 Capability Bean并将其置于 @FeignClient 配置中,可以让你注册它们并修改相关客户端的行为

@ConfigurationpublicclassFooConfiguration{@BeanCapabilitycustomCapability(){returnnewCustomCapability();}}

九、Feign Metrics

如果以下所有条件为 true,就会创建并注册一个 MicrometerCapability Bean,这样你的 Feign 客户端就可以被 Micrometer 观察到:

  • feign-micrometer 在 classpath 上。
  • MeterRegistry bean 可用。
  • feign micrometer 属性设置为 true (默认) - spring.cloud.openfeign.micrometer.enabled=true (针对所有客户)- spring.cloud.openfeign.client.config.feignName.micrometer.enabled=true (针对单个客户端)

如果你的应用程序已经使用了 Micrometer,启用这个功能就像把 feign-micrometer 放到你的classpath上一样简单。

你也可以通过以下两种方式禁用该功能:

  • 从你的 classpath 中排除 feign-micrometer。
  • 将 feign micrometer 一个属性设置为 false - spring.cloud.openfeign.micrometer.enabled=false- spring.cloud.openfeign.client.config.feignName.micrometer.enabled=false

注意:spring.cloud.openfeign.micrometer.enabled=false 禁用所有 Feign 客户端的 Micrometer 支持,而不考虑客户端级标志的值:spring.cloud.openfeign.client.config.feignName.micrometer.enabled。如果你想启用或禁用每个客户端的 Micrometer 支持,不要设置 spring.cloud.openfeign.micrometer.enabled 并使用 spring.cloud.openfeign.client.config.feignName.micrometer.enabled。

你也可以通过注册你自己的bean来自定义 MicrometerObservationCapability:

@ConfigurationpublicclassFooConfiguration{@BeanpublicMicrometerObservationCapabilitymicrometerObservationCapability(ObservationRegistry 
                                                                           registry){returnnewMicrometerObservationCapability(registry);}}

仍然可以在 Feign 中使用 MicrometerCapability(仅支持指标),你需要禁用 Micrometer 支持(spring.cloud.openfeign.micrometer.enabled=false)并创建一个 MicrometerCapability Bean:

@ConfigurationpublicclassFooConfiguration{@BeanpublicMicrometerCapabilitymicrometerCapability(MeterRegistry meterRegistry){returnnewMicrometerCapability(meterRegistry);}}

十、开启Feign的缓存

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

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

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

十、@SpringQueryMap注解支持

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

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

// Params.javapublicclassParams{privateString param1;privateString param2;// [Getters and setters omitted for brevity]}

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

@FeignClient("demo")publicinterfaceDemoTemplate{@GetMapping(path ="/demo")StringdemoEndpoint(@SpringQueryMapParams params);}

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

十一、HATEOAS 的支持

Spring提供了一些API来创建遵循 HATEOAS 原则的REST表示, Spring Hateoas 和 Spring Data REST。

如果你的项目使用 org.springframework.boot:spring-boot-starter-hateoas starter 或 org.springframework.boot:spring-boot-starter-data-rest starter,Feign HATEOAS 支持被默认启用。

当HATEOAS支持被启用时,Feign 客户端被允许序列化和反序列化 HATEOAS 表示模型: EntityModel、 CollectionModel 和 PagedModel.。

@FeignClient("demo")publicinterfaceDemoTemplate{@GetMapping(path ="/stores")CollectionModel<Store>getStores();}

十二、Spring @MatrixVariable 的支持

Spring Cloud OpenFeign提供对Spring @MatrixVariable 注解的支持。

如果一个 map 被作为方法参数传递,@MatrixVariable 的路径片段是通过用 = 连接 map 中的键值对来创建的。

如果传递了一个不同的对象,那么在 @MatrixVariable 注解中提供的 name(如果定义了的话)或者注解的变量名称将使用 = 与提供的方法参数结合起来。

尽管在服务器端,Spring 并不要求用户将路径段占位符的名称与 matrix variable 的名称相同,因为这在客户端太模糊了,Spring Cloud OpenFeign要求你添加一个路径段占位符,其名称要与 @MatrixVariable 注解(如果定义了)中提供的 name 或注解的变量名称相符。例如:

@GetMapping("/objects/links/{matrixVars}")Map<String,List<String>>getObjects(@MatrixVariableMap<String,List<String>> matrixVars);

注意,变量名和 path 段占位符都被称为 matrixVars。

@FeignClient("demo")publicinterfaceDemoTemplate{@GetMapping(path ="/stores")CollectionModel<Store>getStores();}

十三、FeignCollectionFormat的支持

我们通过提供 @CollectionFormat 注解来支持 feign.CollectionFormat。你可以通过传递所需的 feign.CollectionFormat 作为注解值,用它来注解一个 Feign 客户端方法(或整个类来影响所有方法)。

在下面的例子中,使用 CSV 格式而不是默认的 EXPLODED 来处理这个方法。

@FeignClient(name ="demo")protectedinterfaceDemoFeignClient{@CollectionFormat(feign.CollectionFormat.CSV)@GetMapping(path ="/test")ResponseEntityperformRequest(String test);}

十四、响应式的支持

由于 OpenFeign项目 目前不支持响应式客户端,如 Spring WebClient,Spring Cloud OpenFeign也不支持。一旦核心项目中可用,我们将在这里添加对它的支持。

初始化错误

根据你使用 Feign 客户端的方式,你可能会在启动你的应用程序时看到初始化错误。为了解决这个问题,你可以在自动连接客户端时使用一个 ObjectProvider。

@AutowiredObjectProvider<TestFeignClient> testFeignClient;

十五、Spring Data 的支持

如果 Jackson Databind 和 Spring Data Commons 在classpath上,org.springframework.data.domain.Page 和 org.springframework.data.domain.Sort 的 converter 将被自动添加。

要禁用这种行为,请设置:

spring.cloud.openfeign.autoconfiguration.jackson.enabled=false

详见 org.springframework.cloud.openfeign.FeignAutoConfiguration.FeignJacksonConfiguration。

十六、Spring@RefreshScope的支持

如果启用了Feign客户端刷新,每个Feign客户端的创建都有:

  • feign.Request.Options 作为一个 refresh scope 的bean。这意味着诸如 connectTimeout 和 readTimeout 等属性可以针对任何Feign客户端实例进行刷新。
  • 在 org.springframework.cloud.openfeign.RefreshableUrl 下包装的url。这意味着如果用 spring.cloud.openfeign.client.config.{feignName}.url 属性定义 Feign 客户端的URL,可以针对任何 Feign 客户端实例进行刷新。

你可以通过 POST /actuator/refresh 刷新这些属性。

默认情况下,Feign 客户端的刷新行为是禁用的。使用以下属性来启用刷新行为:

spring.cloud.openfeign.client.refresh-enabled=true

注意:不要在@FeignClient 接口上使用 @RefreshScope 注解

十七、支持向 Feign 客户端提供URL的方法

你可以通过以下任何一种方式向Feign客户端提供一个URL:
场景例子细节URL是在 @FeignClient 注解中提供的。@FeignClient(name=“testClient”, url=“http://localhost:8081”)URL是从注解的 url 属性中解析出来的,没有负载均衡。URL是在 @FeignClient 注解和配置属性中提供的。@FeignClient(name=“testClient”, url=“http://localhost:8081”) 和定义在 application.yml 中的属性 spring.cloud.openfeign.client.config.testClient.url=http://localhost:8081URL是从注解的 url 属性中解析出来的,没有负载均衡。在配置属性中提供的URL仍未使用。URL没有在 @FeignClient 注解中提供,而是在配置属性中提供。@FeignClient(name=“testClient”) 和定义在 application.yml 中的属性 spring.cloud.openfeign.client.config.testClient.url=http://localhost:8081URL 从配置属性中解析,没有负载均衡。如果 spring.cloud.openfeign.client.refresh-enabled=true,那么配置属性中定义的 URL 可以被刷新,如 Spring RefreshScope 的支持 中所述。在 @FeignClient 注解中和配置属性中都没有提供这个URL。@FeignClient(name=“testClient”)URL是从注解的 name 属性中解析出来的,具有负载均衡性。

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

1、path路径上携带参数

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

2、单个简单数据类型

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

3、多个简单数据类型

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

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

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

5、JavaBean对象

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

6、多path路径上携带参数

/**
 * 服务提供方:多path路径上携带参数
 */@GetMapping("/test6/{myId}/test66/{myId2}")publicStringtest6(@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}")Stringtest6(@PathVariable("myId")String myId,@PathVariable("myId2")String myId2);

7、post获取请求体

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

测试一下吧

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 =newStudent();
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实践

demo-learn-feign

pom.xml
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.9.RELEASE</version><relativePath/></parent><modelVersion>4.0.0</modelVersion><groupId>com.zzhua</groupId><artifactId>demo-learn-feign</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version><spring-cloud.version>Hoxton.SR8</spring-cloud.version><alibaba.version>2.2.5.RELEASE</alibaba.version></properties><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${alibaba.version}</version><type>pom</type><scope>import</scope></dependency><!-- springCloud --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
application.yml
server:port:8095spring:application:name: feign-service
  cloud:nacos:server-addr: localhost:8848hystrix:command:default:circuitBreaker:# 触发熔断的最小请求次数,默认20  每10srequestVolumeThreshold:2# 熔断多少秒后再次去尝试请求sleepWindowInMilliseconds:10000# 触发熔断的失败请求最小占比,默认50%errorThresholdPercentage:10feign:hystrix:# 开启服务降级enabled:trueclient:config:# (配置文件的优先级高于代码中的配置)# 所有feign客户端的默认配置#default:# 全局设置连接超时时间#connectTimeout: 2000# 全局设置读取响应超时时间#readTimeout: 3000# 仅针对name为configN的feign客户端(优先级高于default配置)configN:# 设置日志级别, 但前提是feign客户端日志级别必须是debug才会打印出来loggerLevel: BASIC
        # 设置读取响应超时时间, 超过5s将会抛出异常SocketTimeoutException: Read timed outreadTimeout:5000retryN:# 自定义错误解码器errorDecoder: com.zzhua.feign03.CustomErrorDecoder
        # 设置重试器为Retryer.Default(默认的重试器), 或 Retryer.NEVER_RETRY(不重试)retryer: feign.Retryer.Default
        # 开启重试条件retryable:true# 最多重试4次#maxAttempts: 4#backoff:# 开启退避算法#enabled: true# 初始重试间隔时间为1秒#delay: 1000# 最大重试间隔时间为5秒#maxDelay: 5000# 重试间隔时间按2的指数增长#multiplier: 2.0
logback.xml
<?xml version="1.0" encoding="UTF-8"?><configurationdebug="false"><!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径--><propertyname="LOG_BASE_PATH"value="logs/"/><propertyname="maxFileSize"value="5MB"/><propertyname="maxHistory"value="30"/><propertyname="commonPattern"value="%d{HH:mm:ss.SSS}-${PID}-[%thread] %-5level %logger{30} - %msg%n"/><!--控制台日志, 控制台输出 --><appendername="STDOUT"class="ch.qos.logback.core.ConsoleAppender"><encoderclass="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度,%msg:日志消息,%n是换行符--><pattern>${commonPattern}</pattern></encoder></appender><!--文件日志, 按照每天生成日志文件 --><appendername="FILE"class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${LOG_BASE_PATH}/feignApp.log</file><rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><!--日志文件输出的文件名--><FileNamePattern>${LOG_BASE_PATH}/%d/xxx.log.%d{yyyy-MM-dd}-%i.log</FileNamePattern><!--日志文件保留天数--><MaxHistory>${maxHistory}</MaxHistory><timeBasedFileNamingAndTriggeringPolicyclass="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>${maxFileSize}</maxFileSize></timeBasedFileNamingAndTriggeringPolicy></rollingPolicy><encoderclass="ch.qos.logback.classic.encoder.PatternLayoutEncoder"><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>${commonPattern}</pattern></encoder></appender><!-- 在这里单独配置某个feign客户端, 让Feign能够打印日志, 至于打印多少日志, 就要设置Logger.Level了;
         这里的name不能使用通配符哦, 但是可以设置为: com.zzhua.feign02, 它会将这个包下的所有类都设置为指定的日志级别
     --><loggername="com.zzhua.feign02.ConfigFeignClient"level="DEBUG"additivity="false"><appender-refref="STDOUT"/></logger><loggername="com.zzhua.feign03.RetryFeignClient"level="DEBUG"additivity="false"><appender-refref="STDOUT"/></logger><!-- 日志输出级别 --><rootlevel="DEBUG"><appender-refref="STDOUT"/><appender-refref="FILE"/></root></configuration>
FeignApp
@SpringBootApplication@EnableFeignClientspublicclassFeignApp{publicstaticvoidmain(String[] args){SpringApplication.run(FeignApp.class, args);}}
feign01
IndexController
@RestControllerpublicclassIndexController{@AutowiredprivateStaticRemoteFeignClient remoteFeignClient;@GetMapping("findPerson")publicPersonfindPerson(){Person person = remoteFeignClient.findPerson("zzhua",26);return person;}@GetMapping("getPerson")publicPersongetPerson(){Person person = remoteFeignClient.getPerson(newPerson("zzhua",26,newAddress("CN","HN")));return person;}@GetMapping("getPerson1")publicPersongetPerson1(){Person person = remoteFeignClient.getPerson1("zzhua",26,newAddress("CN","HN"));return person;}@GetMapping("getPerson2")publicPersongetPerson2(){HashMap<String,Object> map =newHashMap<>();
        map.put("name","zzhua");
        map.put("age",26);
        map.put("address",newAddress("CN","HN"));Person person = remoteFeignClient.getPerson2(map);return person;}@GetMapping("addPerson")publicPersonaddPerson(){Person person = remoteFeignClient.addPerson(newPerson("zzhua",26,newAddress("CN","HN")),2);return person;}@GetMapping("addPerson2")publicPersonaddPerson2(){Person person = remoteFeignClient.addPerson2(newPerson("zzhua",26,newAddress("CN","HN")),newAddress("CN2","HN2"));return person;}@GetMapping("checkPerson")publicPersoncheckPerson(){return remoteFeignClient.checkPerson(newPerson("zzhua",26,newAddress("CN","HN")),newInteger[]{1,2,3});}@GetMapping("checkPerson2")publicPersoncheckPerson2(){return remoteFeignClient.checkPerson2(newPerson("zzhua",26,newAddress("CN","HN")),Arrays.asList(1,2,3));}}
StaticRemoteFeignClient
/* 重点是测试参数写法 */@FeignClient(name ="remote",url ="http://localhost:8084",path ="/orderService/remote")publicinterfaceStaticRemoteFeignClient{// 接口提供方: http://localhost:8084/orderService/remote/findPerson?pName=zzhua&pAge=26 能通@GetMapping("findPerson")// 多于1个参数,则必须写@RequestParam注解(并且必须写value)PersonfindPerson(@RequestParam(name ="pName")String name,@RequestParam(name ="pAge")Integer age);@GetMapping("getPerson")// @RequestParam后面是自定义参数类型将不会封装到接口的方法参数中PersongetPerson(@RequestParam("person")Person person);@GetMapping("getPerson1")// feign将会把url拼接成url?name=xx&age=yy&address=zz(address字符串形式)//   这个address将会导致接口那边在获取address时,不能正常封装成Address对象而导致报错PersongetPerson1(@RequestParam(name ="name")String name,@RequestParam(name ="age")Integer age
                   ,@RequestParam(name ="address")Address address);@PostMapping("getPerson2")// 可以使用Map封装(远程接口使用@RequestBody Map来接收(address属性能正常接收到))PersongetPerson2(Map<String,Object> map);@PostMapping("addPerson")// 不能使用超过1个@RequestBodyPersonaddPerson(@RequestBodyPerson person,@RequestParam("pAge")Integer age);@PostMapping("addPerson2")// @RequestParam后面是自定义参数Address将不会封装到接口的方法参数中PersonaddPerson2(@RequestBodyPerson person,@RequestParam("addr")Address addr);@PostMapping("checkPerson")// 多于1个参数,则必须写@RequestParam注解(并且必须写value)// feign拼接url?ids=1%2C2%2C3 (%2C,即逗号)PersoncheckPerson(@RequestBodyPerson person,@RequestParam("ids")Integer[] ids);@PostMapping("checkPerson2")// 多于1个参数,则必须写@RequestParam注解(并且必须写value)// feign拼接url?ids=1&ids=2&ids=3PersoncheckPerson2(@RequestBodyPerson person,@RequestParam("ids")List<Integer> ids);}
Person
@Data@ToString@AllArgsConstructor@NoArgsConstructorpublicclassPerson{privateString name;privateInteger age;privateAddress address;}
Address
@Data@NoArgsConstructor@AllArgsConstructorpublicclassAddress{privateString country;privateString province;}
feign02
ConfigController
@RestControllerpublicclassConfigController{@AutowiredprivateConfigFeignClient configFeignClient;// 测试日志配置// (需要先配置对应的feign客户端的日志器的日志级别为DEBUG,//  然后再通过java代码的方式配置1个Logger.Level的bean到@FeignClient注解指定的配置类中即可,//  或者通过配置文件的方式配置://      在配置文件中配置feign.client.config.{feignName}.loggerLevel: BASIC//      或者在配置文件中配置feign.client.config.default.loggerLevel: BASIC(全局))@GetMapping("testLog")publicObjecttestLog(){return configFeignClient.testLog();}// 测试响应超时配置// (在配置文件中配置: 可以全局配置, 也可以给单个指定的feign客户端配置;//  在代码中配置: 可以给全局配置, 也可以给单个指定的feign客户端配置;//  在代码中给feign接口中的单个方法指定超时时间(如下面的testTimeout2方法所示);//  当超过指定的时间还没有返回时, 就会抛出异常;//  默认情况下(在配置文件和代码中都不配置超时时间), 默认的读取响应超时时间为60秒, 连接超时时间为10s,//          也就是说, 如果发起响应后, 60秒之内还没有给出响应, 就会抛出异常了;)@GetMapping("testTimeout")publicObjecttestTimeout(Long sec){return configFeignClient.testTimeout(sec);}@GetMapping("testTimeout2")publicObjecttestTimeout2(Long sec){Request.Options options =newRequest.Options(2,TimeUnit.SECONDS,3,TimeUnit.SECONDS,true);return configFeignClient.testTimeout2(options, sec);}}
ConfigFeignClient
/* 重点是验证各种配置 */@FeignClient(name ="configN", url ="http://localhost:8084", path ="/orderService/config",
        configuration ={ConfigFeignClientConfig.class})publicinterfaceConfigFeignClient{// 访问: http://localhost:8084/orderService/config/testLog 能通@GetMapping("/testLog")StringtestLog();// 访问: http://localhost:8084/orderService/config/testTimeout?sec=1 能通@GetMapping("/testTimeout")StringtestTimeout(@RequestParam("sec")Long sec);@GetMapping("/testTimeout")StringtestTimeout2(Request.Options options,@RequestParam("sec")Long sec);}
ConfigFeignClientConfig
publicclassConfigFeignClientConf{/* 配置feign客户端打印日志的注意点:
         1. feign客户端只有在配置feign客户端全类名对应的日志级别为debug的时候, 才有可能输出日志
         2. 我们需要在配置类中如下定义1个Logger.Level的bean即可。
            或者, 在配置文件中配置feign.client.config.{feignName}.loggerLevel: BASIC
            或者, 在配置文件中配置feign.client.config.{feignName}.loggerLevel: BASIC(全局)
     */@BeanLogger.LevelfeignLoggerLevel(){// return Logger.Level.NONE;// return Logger.Level.BASIC;// return Logger.Level.HEADERS;returnLogger.Level.FULL;}/* 也可以通过代码的方式配置超时时间 *//*@Bean
    public Request.Options requestOptions() {
        return new Request.Options(2, TimeUnit.SECONDS, 3, TimeUnit.SECONDS, true);
    }*/}
feign03
RetryController
@RestControllerpublicclassRetryController{@AutowiredprivateRetryFeignClient retryFeignClient;// 测试重试机制(默认不会重试)// Feign的重试机制主要包括以下几个方面://      1. 配置重试次数和重试间隔时间//      2. 配置重试条件和重试策略//      3. 实现重试回退机制(这个不包括当前示例中)// 什么情况下会重试?//      默认情况下只有超时的情况下才会重试, 而如果被调用方抛出了其它异常, 则不会重试//         (其实是否重试, 不是由被调用方来决定的, 而是由调用方来确定的;//         【默认情况下超时会引起重试】: 发起feign调用后, SynchronousMethodHandler#executeAndDecode 执行请求时,//         会由于被调用方一直没有响应而抛出IO异常,捕获到IO异常后,转而抛出 RetryException,//         抛出的RetryException在 SynchronousMethodHandler#invoke 的while(true)循环中又被捕捉到,从而交给retryer继续重试;//         【被调用方抛出其它异常不会引起重试】: 发起feign调用后, SynchronousMethodHandler#executeAndDecode执行请求时,//         会获得响应对象, 并将此响应对象交给 AsyncResponseHandler#handleResponse处理, 其中会根据响应状态码作不同处理,//         如果状态码是200到300之间, 那么正常处理, 如果不是, 则会交给ErrorDecoder处理返回1个异常, 然后以异常结束, 然后返回到//         SynchronousMethodHandler#invoke 的while(true)循环中, 如果这个异常是 RetryException, 则交给retryer继续重试, 如果不是, 则跳出while循环;//         【因此, 我们如果要重试就需要自己实现ErrorDecoder, 在需要重试的时候, 返回1个RetryException即可】)// 如何配置重试?//      可以通过配置文件的方式配置errorDecoder和retryer;//      可以通过代码的方式配置1个errorDecoder的bean和1个Retryer的bean(这里的retryer可以配置重试次数)//     (使用配置文件配置的无效backoff无效、使用@Retryable注解配置的也无效, 后面再看)@GetMapping("testRetry")publicObjecttestRetry(Integer flag,Integer sec){return retryFeignClient.testRetry(flag, sec);}@GetMapping("testRetry2")publicObjecttestRetry2(Integer flag,Integer sec){return retryFeignClient.testRetry2(flag, sec);}}
RetryFeignClient
@FeignClient(name ="retryN", url ="http://localhost:8084", path ="/orderService/config",
        configuration ={RetryConfig.class})publicinterfaceRetryFeignClient{// 访问: http://localhost:8084/orderService/config/testRetry?flag=1 能通@GetMapping("/testRetry")StringtestRetry(@RequestParam("flag")Integer flag,@RequestParam("sec")Integer sec);// 访问: http://localhost:8084/orderService/config/testRetry?flag=1 能通@GetMapping("/testRetry")// @Retryable(value = {IOException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000, maxDelay = 5000, multiplier = 2))StringtestRetry2(@RequestParam("flag")Integer flag,@RequestParam("sec")Integer sec);}
RetryConfig
publicclassRetryConfig{@BeanLogger.LevelfeignLoggerLevel(){// return Logger.Level.NONE;returnLogger.Level.BASIC;// return Logger.Level.HEADERS;// return Logger.Level.FULL;}/* 设置5s的超时时间, 以测定什么情况下会重试 */@BeanpublicRequest.OptionsrequestOptions(){returnnewRequest.Options(2,TimeUnit.SECONDS,5,TimeUnit.SECONDS,true);}/*
        使用 Retryer.Default 类生成一个默认的重试器,
        它会在当前请求失败后重试 3 次,并会在第一次重试前等待 500 毫秒,
        在第二次重试前等待 1000 毫秒,第三次重试前等待 2000 毫秒,以此类推
    *//*@Bean
    public Retryer retryer() {
        return new Retryer.Default(500, 5000, 3); // 最多请求3次
    }*//*@Bean
    public CustomErrorDecoder customErrorDecoder() {
        return new CustomErrorDecoder();
    }*/}
CustomErrorDecoder
@Slf4jpublicclassCustomErrorDecoderimplementsErrorDecoder{@OverridepublicExceptiondecode(String methodKey,Response response){if(response.status()==500){

            log.info("重试一波...");// 认为有必要重试就返回RetryableExceptionreturnnewRetryableException(500,"",
                    response.request().httpMethod(),null,
                    response.request());}

        log.info("认为没必要重试...");returnnewRuntimeException("Unknown error");}}
feign04
FallbackController
@RestControllerpublicclassFallbackController{@AutowiredprivateFallbackFeignClient fallbackFeignClient;// 测试降级、熔断// 需要配置feign.hystrix.enabled: true, 来开启hystrix, 否则被调用方抛出异常后, 不会走指定的降级逻辑//      访问1: http://localhost:8095/testFallback?flag=1&sec=0 正常返回 "ok"//      访问2: http://localhost:8095/testFallback?flag=-1&sec=0 首先发1个请求给被调用方, 返回500状态码, 然后走降级逻辑 正常返回 "ojdk"//      测试:  当在浏览器上多次快速点击访问2时, 刚开始, 请求会发起远程调用, 当多次后就会直接熔断, 然后直接走降级的方法, 多次都熔断之后, 我再访问1, 也是直接熔断走了降级方法。//            但是熔断结束的时间很短, 稍微停一下, 就会发起远程调用。熔断的目的是为了保护下游服务, 在统计到下游服务多次响应失败后, 就会熔断一段时间, 等过会儿再去访问。//            我们可以通过 hystrix.command.default.circuitBreaker.xxx来配置熔断相关的东西(如配置文件中所示)// 降级://    可以使用@FeignClient的fallback来直接指定降级的类, 必须定义为bean且实现feign接口//    也可以使用@FeignClient的fallbackFactory来直接指定降级的工厂类, 必须定义为bean且实现FallbackFactory, 并且在重写的方法中返回feign接口的实现类, 好处是可以拿到发生错误的异常//    fallback与fallbackFactory之间只能使用1个@GetMapping("testFallback")publicObjecttestFallback(Integer flag,Integer sec){return fallbackFeignClient.testFallback(flag, sec);}}
FallbackFeignClient
@FeignClient(name ="fallbackN", url ="http://localhost:8084", path ="/orderService/config",
        configuration ={FallbackConfig.class},fallback =FallbackFeignClientImpl.class// ,fallbackFactory = FallbackFeignClientFactory.class)publicinterfaceFallbackFeignClient{// 访问: http://localhost:8084/orderService/config/testFallback?flag=1 能通@GetMapping("/testFallback")StringtestFallback(@RequestParam("flag")Integer flag,@RequestParam("sec")Integer sec);}
FallbackConfig
publicclassFallbackConfig{@BeanLogger.LevelfeignLoggerLevel(){// return Logger.Level.NONE;// return Logger.Level.BASIC;// return Logger.Level.HEADERS;returnLogger.Level.FULL;}}
FallbackFeignClientImpl
// 容器中同时存在FallbackFeignClient接口实现的2个bean, 其中1个是feign的动态代理, 1个就是当前这个类// 而feign的动态代理具有primary, 所以注入时, 会优先@Slf4j@ComponentpublicclassFallbackFeignClientImplimplementsFallbackFeignClient{@OverridepublicStringtestFallback(Integer flag,Integer sec){
        log.info("降级使用fallback...");return"ojdk";}}
FallbackFeignClientFactory
@Slf4j@ComponentpublicclassFallbackFeignClientFactoryimplementsFallbackFactory<FallbackFeignClient>{@OverridepublicFallbackFeignClientcreate(Throwable throwable){
        log.info("获取到异常: {}", throwable.toString());returnnewFallbackFeignClient(){@OverridepublicStringtestFallback(Integer flag,Integer sec){
                log.info("FallbackFeignClientFactory$1处理异常了...");return"nook";}};}}
feign05
LbController
@RestControllerpublicclassLbController{@AutowiredprivateLbFeignClient lbFeignClient;// 测试负载均衡// 访问: http://localhost:8095/testLb,// 当没有服务时, 直接走的降级;// 当启动1个order-service服务时, 立即访问, 仍然走的是降级, 过了一小段时间后, 成功调用到服务;// 当再启动1个order-service服务时, 立即访问, 仍然调用的是第1个服务, 过了一小段时间后, 才会轮询访问;// 当其中1个服务挂掉的时候, 还是会走降级逻辑(一直走降级方法), 过了一小段时间后, 一直成功调用到还存活的服务// (这个是开启了feign.hystrix.enabled: true时的现象, 大概是因为只要有1个服务挂了, 就认为服务不可用, 然后直接熔断走的降级。//  然后, 又试着关闭feign.hystrix.enabled: false, 再测试一遍, 负载均衡访问后, 关闭1个order-service, 在轮询到关闭的服务时, 稍微会等待一小会儿,//  就把请求打到了还存活的服务上。);@GetMapping("testLb")publicObjecttestLb(){return lbFeignClient.testLb();}// 测试降级、熔断// 需要配置feign.hystrix.enabled: true, 来开启hystrix, 否则被调用方抛出异常后, 不会走指定的降级逻辑//      访问1: http://localhost:8095/testLb2?flag=1&sec=0    正常//      访问2: http://localhost:8095/testLb2?flag=-1&sec=0   故意报错// 行为同FallbackController中一致@GetMapping("testLb2")publicObjecttestLb2(Integer flag,Integer sec){return lbFeignClient.testLb2(flag, sec);}}
LbFeignClient
@FeignClient(name ="order-service", path ="/orderService/config",
        fallback =LbFeignClientImpl.class,
        configuration =LbConfig.class)publicinterfaceLbFeignClient{@GetMapping("testLb")StringtestLb();@GetMapping("testLb2")StringtestLb2(@RequestParam("flag")Integer flag,@RequestParam("sec")Integer sec);}
LbConfig
publicclassLbConfig{@BeanLogger.LevelfeignLoggerLevel(){// return Logger.Level.NONE;returnLogger.Level.BASIC;// return Logger.Level.HEADERS;// return Logger.Level.FULL;}}
LbFeignClientImpl
@Slf4j@ComponentpublicclassLbFeignClientImplimplementsLbFeignClient{@OverridepublicStringtestLb(){
        log.info("lb2降级...");return"lb~ok-降级";}@OverridepublicStringtestLb2(Integer flag,Integer sec){
        log.info("lb2降级...");return"lb2~ok-降级";}}

order-service

pom.xml
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.9.RELEASE</version><relativePath/></parent><modelVersion>4.0.0</modelVersion><groupId>com.zzhua</groupId><artifactId>order-service</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version><spring-cloud.version>Hoxton.SR8</spring-cloud.version><alibaba.version>2.2.5.RELEASE</alibaba.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${alibaba.version}</version><type>pom</type><scope>import</scope></dependency><!-- springCloud --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
application.yml
server:port:8084servlet:context-path: /orderService
spring:application:name: order-service
  cloud:nacos:server-addr: localhost:8848
RemoteController
@RestController@RequestMapping("remote")publicclassRemoteController{@GetMapping("findPerson")PersonfindPerson(String pName,Integer pAge){returnnewPerson(pName, pAge,newAddress("CN","HN"));}@GetMapping("getPerson")PersongetPerson(Person person){System.out.println(person);return person;}@PostMapping("getPerson2")PersongetPerson2(@RequestBodyMap<String,Object> cMap){System.out.println(cMap);returnnewPerson();}@PostMapping("addPerson")PersonaddPerson(@RequestBodyPerson person,Integer pAge){System.out.println(person);System.out.println(pAge);if(person.getAge()!=null){
            person.setAge( person.getAge()+1);}return person;}@PostMapping("addPerson2")PersonaddPerson2(@RequestBodyPerson person,Address addr){System.out.println(person);System.out.println(addr);if(person !=null){
            person.setAddress(addr);}return person;}@PostMapping("checkPerson")PersoncheckPerson(@RequestBodyPerson person,Integer[] ids){System.out.println(person);System.out.println(StringUtils.arrayToCommaDelimitedString(ids));return person;}@PostMapping("checkPerson2")// List<Integer> ids必须要带@RequestParam注解才能接收到PersoncheckPerson2(@RequestBodyPerson person,@RequestParam("ids")List<Integer> ids){System.out.println(person);System.out.println(ids);return person;}}
ConfigController
@Slf4j@RestController@RequestMapping("config")publicclassConfigController{@GetMapping("testLog")StringtestLog(){return"ok";}@GetMapping("/testTimeout")StringtestTimeout(Long sec){try{TimeUnit.SECONDS.sleep(sec);}catch(InterruptedException e){
            e.printStackTrace();}return"ok";}@GetMapping("/testRetry")StringtestRetry(Integer flag,@RequestParam(required =false, defaultValue ="0")Integer sec)throwsException{
        log.info("testRetry请求: {}", flag);if(sec ==0){if(flag >=0){return"ok";}elseif(Objects.equals(flag,-1)){thrownewRuntimeException("flag为-1");}elseif(Objects.equals(flag,-2)){thrownewNullPointerException("flag为-2");}elseif(Objects.equals(flag,-3)){thrownewIOException("flag为-3");}thrownewIndexOutOfBoundsException("越界异常");}else{TimeUnit.SECONDS.sleep(sec);return"ok2";}}@GetMapping("/testFallback")StringtestFallback(Integer flag,@RequestParam(required =false, defaultValue ="0")Integer sec)throwsException{
        log.info("testFallback请求: {}", flag);if(sec ==0){if(flag >=0){return"ok";}elseif(Objects.equals(flag,-1)){thrownewRuntimeException("flag为-1");}elseif(Objects.equals(flag,-2)){thrownewNullPointerException("flag为-2");}elseif(Objects.equals(flag,-3)){thrownewIOException("flag为-3");}thrownewIndexOutOfBoundsException("越界异常");}else{TimeUnit.SECONDS.sleep(sec);return"ok2";}}@Value("${lb.name:unknown}")privateString lbName;@GetMapping("testLb")StringtestLb(){return"loadBalance~"+ lbName;}@GetMapping("/testLb2")StringtestLb2(Integer flag,@RequestParam(required =false, defaultValue ="0")Integer sec)throwsException{
        log.info("testLb2请求: {}", flag);if(sec ==0){if(flag >=0){return"ok";}elseif(Objects.equals(flag,-1)){thrownewRuntimeException("flag为-1");}elseif(Objects.equals(flag,-2)){thrownewNullPointerException("flag为-2");}elseif(Objects.equals(flag,-3)){thrownewIOException("flag为-3");}thrownewIndexOutOfBoundsException("越界异常");}else{TimeUnit.SECONDS.sleep(sec);return"ok2";}}}
OrderApp
@SpringBootApplicationpublicclassOrderApp{publicstaticvoidmain(String[] args){SpringApplication.run(OrderApp.class, args);}}

测试

启动nacos,再启动order-service,再启动demo-learn-feign即可

标签: 学习

本文转载自: https://blog.csdn.net/qq_16992475/article/details/136286923
版权归原作者 ps酷教程 所有, 如有侵权,请联系我们删除。

“Spring Cloud OpenFeign官方文档学习”的评论:

还没有评论