引入依赖
build.gradle1
| implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
|
自定义Http Client
Apache Http Client
1
| implementation 'io.github.openfeign:feign-httpclient'
|
OkHttp
1
| implementation 'io.github.openfeign:feign-okhttp'
|
FeignClient接口扫描
1 2 3 4 5 6 7 8 9
| @EnableFeignClients(basePackageClasses = { Application.class, // 本项目根路径 OtherClient.class // 其他包的Client }) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
|
注意:使用@ComponentScan无效
Feign接口仅支持继承一个接口
Contract.parseAndValidateMetadata
自定义feign超时
全局默认
1 2
| feign.client.config.default.connect-timeout=10000 feign.client.config.default.read-timeout=60000
|
指定FeignClient的配置
1 2
| feign.client.config.myContextId.connect-timeout=10000 feign.client.config.myContextId.read-timeout=60000
|
Spring构造Feign Client流程
org.springframework.cloud.openfeign.FeignClientFactoryBean#configureFeign
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| protected void configureFeign(FeignContext context, Feign.Builder builder) { FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class) : applicationContext.getBean(FeignClientProperties.class);
FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class); setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
if (properties != null && inheritParentContext) { if (properties.isDefaultToProperties()) { configureUsingConfiguration(context, builder); configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder); configureUsingProperties(properties.getConfig().get(contextId), builder); } else { configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder); configureUsingProperties(properties.getConfig().get(contextId), builder); configureUsingConfiguration(context, builder); } } else { configureUsingConfiguration(context, builder); } }
|
问题记录
请求body为空对象时报错
问题复现:
Feign接口定义如下
1 2
| @PostMapping("call") Response call(@RequestBody Request request);
|
其中 Request 为空对象
1 2 3 4
| @Data public class Request {
}
|
调用方执行时会出错
1
| feign.codec.EncodeException: Could not write request: no suitable HttpMessageConverter found for request type
|
解决方法
方案1:调用方增加配置(推荐)
1
| spring.jackson.serialization.fail-on-empty-beans=false
|
方案2:body不使用空对象
原理分析
Spring Feign使用HttpMessageConverters处理请求体,最终我们希望是MappingJackson2HttpMessageConverter来序列化我们的body对象,但前提需要通过它的canWrite方法,方法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Override public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) { if (!canWrite(mediaType)) { return false; } if (mediaType != null && mediaType.getCharset() != null) { Charset charset = mediaType.getCharset(); if (!ENCODINGS.containsKey(charset.name())) { return false; } } AtomicReference<Throwable> causeRef = new AtomicReference<>(); if (this.objectMapper.canSerialize(clazz, causeRef)) { return true; } logWarningIfNecessary(clazz, causeRef.get()); return false; }
|
注意其中的canSerialize方法,当ObjectMapper开启fail-on-empty-beans时(也是默认开启),canSerialize(Object.class)将返回false
所以我们需要关闭Spring管理的ObjectMapper的fail-on-empty-beans选项