SpringCloud-OpenFeign官方文档使用大全详解
Feign是一个声明式的web服务客户端,让编写web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
文章目录
- 一、OpenFeign简介
- 二、Springboot集成OpenFeign
- 三、覆盖默认配置
- 四、超时处理
- 五、手动创建feign客户端
- 六、Feign的SpringCloud断路器
- 七、Feign的继承重用
- 八、Feign请求响应的压缩
- 九、开启Feign的缓存
- 十、@SpringQueryMap注解支持
- 十一、FeignClient的参数传递给服务提供方的方式
- 十二、修改客户端组件
- 参考资料
一、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
更多推荐
所有评论(0)