目录
no suitable HttpMessageConverter found for response type
本章内容讲解重点目标以及面向用户
本章目标 | 了解feign的原理 掌握feign的使用 学会feign多种header的设置方式 学会feign日志控制 了解feign高阶配置项 其他开发注意事项 |
面向用户 | 初、中、高阶的研发同学 |
很多时候,我们查看一些高阶架构同学在写Http服务调用时,调试跟踪代码时发现就只有接口签名,没有想okhttp那样写一些调用过程的代码,代码看起来风格清爽、优雅并不失逻辑严谨,读完该篇文章,您会掌握其实现方案,并提供demo协助您完成实战,有技术问题可以私信请教;
一、SpringBoot快速整合Feign
1.添加Pom依赖
<properties>
<spring-cloud-feign.version>3.1.3</spring-cloud-feign.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.10</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>${spring-cloud-feign.version}</version>
</dependency>
</dependencies>
2.启动类添加注解
@EnableFeignClients
3.引用Feign服务
@RestController
@RequestMapping("/stock")
public class StockController {
@Autowired
private LocalFeignService service;
@GetMapping("/get")
public String get(){
service.add("10");
return "Hello world";
}
}
二、为请求添加Header的3种方式
1.添加固定header
@RestController
public class HomeController {
@Resource
private FeignClientService feignClientService;
@RequestMapping(value = "/add")
public String add(@RequestBody FeignReq req) {
return "ok";
}
@RequestMapping("/test-add")
public Object testAdd() {
FeignReq req = new FeignReq();
req.setId(System.currentTimeMillis());
return feignClientService.add(req);
}
}
在@PostMapping注解的headers参数中添加固定的header值,也包括一些从配置文件读取的配置项作为header值
@Component
@FeignClient(name = "feignClientService", url = "http://localhost:8081")
public interface FeignClientService {
@PostMapping(value = "/add", headers = {"Authorization=myFeignSignToken", "Content-Type=text/plain", "App=${my.name}"})
public String add(@RequestBody FeignReq req);
}
浏览器访问:http://localhost:8081/test-add
2.通过接口签名添加header
@RestController
public class HomeController {
@Resource
private FeignClientService feignClientService;
@PostMapping(value = "/query")
public String query(@RequestParam("queryName") String queryName) {
return queryName + "ok";
}
@RequestMapping("/test-query")
public Object testQuery() {
Map<String, String> map = new HashMap<>();
map.put("token", "ikong_token"+System.currentTimeMillis());
return feignClientService.query("ikong", map);
}
}
重点是在Map类型的headers,这里通过接口签名的@RequestHeader注解将Map对象转化成请求header
@Component
@FeignClient(name = "feignClientService", url = "http://localhost:8081", configuration = TestRequestInterceptor.class)
public interface FeignClientService {
@PostMapping(value = "/query")
public String query(@RequestParam("queryName") String queryName, @RequestHeader Map<String, String> headers);
}
浏览器访问:http://localhost:8081/test-query
3.动态添加header
@RestController
public class HomeController {
@Resource
private FeignClientService feignClientService;
@RequestMapping("/search")
public Object search(@RequestBody FeignReq req) {
return "ok";
}
@RequestMapping("/test-search")
public Object testSearch() {
FeignReq req = new FeignReq();
req.setId(System.currentTimeMillis());
return feignClientService.search(req);
}
}
@Component
@FeignClient(name = "feignClientService", url = "http://localhost:8081", configuration = TestRequestInterceptor.class)
public interface FeignClientService {
@PostMapping("/search")
public String search(@RequestBody FeignReq req);
}
通过FeignClient注解注入configuration = TestRequestInterceptor.class,在TestRequestInterceptor实现header动态添加能力
public class TestRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header(HttpHeaders.AUTHORIZATION, createApiSign());
}
/**
* 创建接口签名
*
* @return
*/
private String createApiSign() {
return UUID.randomUUID().toString();
}
}
浏览器访问:http://localhost:8081/test-search
三、为请求添加超时配置
1.默认超时时间
connectiontimeout : 10s,readtimeout : 60s
public static class Options {
private final int connectTimeoutMillis;
private final int readTimeoutMillis;
public Options(int connectTimeoutMillis, int readTimeoutMillis) {
this.connectTimeoutMillis = connectTimeoutMillis;
this.readTimeoutMillis = readTimeoutMillis;
}
public Options() {
this(10 * 1000, 60 * 1000);
}
/**
* Defaults to 10 seconds. {@code 0} implies no timeout.
*
* @see java.net.HttpURLConnection#getConnectTimeout()
*/
public int connectTimeoutMillis() {
return connectTimeoutMillis;
}
/**
* Defaults to 60 seconds. {@code 0} implies no timeout.
*
* @see java.net.HttpURLConnection#getReadTimeout()
*/
public int readTimeoutMillis() {
return readTimeoutMillis;
}
}
3.超时异常
exception:feign.RetryableException: connect timed out executing POST http://xxx.ik.com/your/api
4.全局超时配置
feign.client.config.default.connectTimeout=5000
feign.client.config.default.readTimeout=5000
5.为单个服务设置超时配置
要实现不同服务配备不同的超时时间,可按如下配置进行
feign.client.config.yourService.connectTimeout=5
feign.client.config.yourService.readTimeout=5
四、为请求配置客户端负载均衡模式
OpenFeign只是一种Rest客户端,本身不具备任何的负载均衡操作。 OpenFeign底层调用的还是Netflix Ribbon负载均衡组件,那我们在使用OpenFeign实现服务调用时,如何修改负载均衡策略呢?
自定义Ribbon的负载均衡配置
@Configuration
// name为服务名
@RibbonClient(name = "my-provider", configuration = MyLoadBalanceConfiguration.class)
public class MyLoadBalanceConfiguration {
@Bean
public IRule ribbonRule() {
return new RandomRule(); // 采用随机策略
}
}
我们这里使用的是随机策略,默认为轮询策略。
Ribbon提供可选负载均衡分类
- 随机策略——RandomRule
- 轮询策略——RoundRobinRule 注:Ribbon默认策略
- 重试策略——RetryRule
- 最低并发策略——BestAvailableRule
- 可用过滤策略——AvailabilityFilteringRule、过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值),性能仅次于最低并发策略。
- 响应时间加权策略——WeightedResponseTimeRule,每隔30秒计算一次服务器响应时间,以响应时间作为权重,响应时间越短的服务器被选中的概率越大。
- 区域权衡策略——ZoneAvoidanceRule
Ribbon的负载均衡策略使用建议
一般情况下,推荐使用最低并发策略,这个性能比默认的轮询策略高很多。
五、Feign日志
feign的日志是建立在 debug日志模式的基础上的,不管是全局配置还是局部配置,代码配置还是属性配置,都需要先把日志模式调成debug模式:(下面与调用FeignClientService接口的日志模式为例)
1.日志级别
OpenFeign的日志级别有
NONE: 默认的,不显示任何日志。
BASIC: 仅记录请求方法、URL、响应状态码以及执行时间。
HEADERS:除了BASIC 中自定义的信息外,还有请求和响应的信息头。
FULL: 除了HEADERS中定义的信息外, 还有请求和响应的正文以及元数据。
2.日志配置类
@Configuration
public class OpenFeignLogConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
3.配置文件
logging:
level:
*[feign日志以什么级别监控哪个接口]:
com.ik.ikong.service.FeignClientService: debug
六、高阶配置
七、Feign与Springboot版本
版本对照关系如下,详情也可以点击这里的链接在官网查看最新版本兼容关系
八、技术问题
在调用接口时候idea报错
no suitable HttpMessageConverter found for response type
解决过程
1.放开日志
@Configuration
public class FeignConfiguration {
@Bean
public feign.Logger logger() {
return new Slf4jLogger();
}
@Bean
public Logger.Level level() {
return Logger.Level.FULL;
}
}
logging:
level:
feign.Logger: debug
日志详细
从放开的日志来看,接口返回的head里:content-type: text/html; charset=UTF-8
虽然我在接口签名上增加了produces、consumes
@PostMapping(value = "/xxxxx/your/api", produces = "application/json;charset=utf-8", consumes = "application/json;charset=utf-8")
可以看到毫无效果,仍旧异常:no suitable HttpMessageConverter found for response type
猜想原生的http返回值只对application/json类型做了泛型的反序列化处理,是对接口返回的content-type: text/html;并不能进行json反序列化处理,遂增加该类型的json反序列化处理;
@Bean
public HttpMessageConverters customConverters() {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_HTML,MediaType.APPLICATION_JSON));
return new HttpMessageConverters(false, Arrays.asList(mappingJackson2HttpMessageConverter, new StringHttpMessageConverter()));
}
重点在mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_HTML,MediaType.APPLICATION_JSON));
最后成功完成增加content-type: text/html的json反序列化;
参考:Spring Cloud