HttpClient、RestTemplate和Feign相关知识
#先了解一下HTTP 协议
史前时期
HTTP 协议在我们的生活中随处可见,打开手机或者电脑,只要你上网,不论是用 iPhone、Android、Windows 还是 Mac,不论是用浏览器还是 App,不论是看新闻、短视频还是听音乐、玩游戏,后面总会有 HTTP 在默默为你服务。
据 NetCraft 公司统计,目前全球至少有 16 亿个网站、2 亿多个独立域名,而这个庞大网络世界的底层运转机制就是 HTTP。
那么,在享受如此便捷舒适的网络生活时,你有没有想过,HTTP 协议是怎么来的?它最开始是什么样子的?又是如何一步一步发展到今天,几乎“统治”了整个互联网世界的呢?
20 世纪 60 年代,美国国防部高等研究计划署(ARPA)建立了 ARPA 网,它有四个分布在各地的节点,被认为是如今互联网的“始祖”。
然后在 70 年代,基于对 ARPA 网的实践和思考,研究人员发明出了著名的 TCP/IP 协议。由于具有良好的分层结构和稳定的性能,TCP/IP 协议迅速战胜其他竞争对手流行起来,并在 80 年代中期进入了 UNIX 系统内核,促使更多的计算机接入了互联网。
创世纪
1989 年,任职于欧洲核子研究中心(CERN)的蒂姆·伯纳斯 - 李(Tim Berners-Lee)发表了一篇论文,提出了在互联网上构建超链接文档系统的构想。这篇论文中他确立了三项关键技术。
- URI:即统一资源标识符,作为互联网上资源的唯一身份;
- HTML:即超文本标记语言,描述超文本文档;
- HTTP:即超文本传输协议,用来传输超文本。
所以在这一年,我们的HTTP诞生了。
HTTP/0.9
20 世纪 90 年代初期的互联网世界非常简陋,计算机处理能力低,存储容量小,网速很慢,还是一片“信息荒漠”。网络上绝大多数的资源都是纯文本,很多通信协议也都使用纯文本,所以 HTTP 的设计也不可避免地受到了时代的限制。
这一时期的 HTTP 被定义为 0.9 版,结构比较简单,为了便于服务器和客户端处理,它也采用了纯文本格式。蒂姆·伯纳斯 - 李最初设想的系统里的文档都是只读的,所以只允许用“GET”动作从服务器上获取 HTML 文档,并且在响应请求之后立即关闭连接,功能非常有限。
HTTP/0.9 虽然很简单,但它作为一个“原型”,充分验证了 Web 服务的可行性,而“简单”也正是它的优点,蕴含了进化和扩展的可能性,因为:
“把简单的系统变复杂”,要比“把复杂的系统变简单”容易得多。
HTTP/1.0
1993 年,NCSA(美国国家超级计算应用中心)开发出了 Mosaic,是第一个可以图文混排的浏览器,随后又在 1995 年开发出了服务器软件 Apache,简化了 HTTP 服务器的搭建工作。
同一时期,计算机多媒体技术也有了新的发展:1992 年发明了 JPEG 图像格式,1995 年发明了 MP3 音乐格式。
这些新软件新技术一经推出立刻就吸引了广大网民的热情,更的多的人开始使用互联网,研究 HTTP 并提出改进意见,甚至实验性地往协议里添加各种特性,从用户需求的角度促进了 HTTP 的发展。
于是在这些已有实践的基础上,经过一系列的草案,HTTP/1.0 版本在 1996 年正式发布。它在多方面增强了 0.9 版,形式上已经和我们现在的 HTTP 差别不大了,例如:
- 增加了 HEAD、POST 等新方法
- 增加了响应状态码,标记可能的错误原因
- 引入了协议版本号概念
- 引入了 HTTP Header(头部)的概念,让 HTTP 处理请求和响应更加灵活
- 传输的数据不再仅限于文本
HTTP/1.1
1995 年,网景的 Netscape Navigator 和微软的 Internet Explorer 开始了著名的“浏览器大战”,都希望在互联网上占据主导地位。于是在“浏览器大战”结束之后的 1999 年,HTTP/1.1 发布了 RFC 文档,编号为 2616,正式确立了延续十余年的传奇。
HTTP/1.1 主要的变更点有:
- 增加了 PUT、DELETE 等新的方法;
- 增加了缓存管理和控制;
- 明确了连接管理,允许持久连接;
- 允许响应数据分块(chunked),利于传输大文件;
- 强制要求 Host 头,让互联网主机托管成为可能。
HTTP/2
HTTP/1.1 发布之后,整个互联网世界呈现出了爆发式的增长,度过了十多年的“快乐时光”,更涌现出了 Facebook、Twitter、淘宝、京东等互联网新贵。
这期间也出现了一些对 HTTP 不满的意见,主要就是连接慢,无法跟上迅猛发展的互联网,但 HTTP/1.1 标准一直“岿然不动”,无奈之下人们只好发明各式各样的“小花招”来缓解这些问题,比如以前常见的切图、JS 合并等网页优化手段。
终于有一天,搜索巨头 Google 忍不住了,首先开发了自己的浏览器 Chrome,然后推出了新的 SPDY 协议,并在 Chrome 里应用于自家的服务器,如同十多年前的网景与微软一样,从实际的用户方来“倒逼”HTTP 协议的变革,这也开启了第二次的“浏览器大战”。
历史再次重演,不过这次的胜利者是 Google,Chrome 目前的全球的占有率超过了 60%。Google 借此顺势把 SPDY 推上了标准的宝座,互联网标准化组织以 SPDY 为基础开始制定新版本的 HTTP 协议,最终在 2015 年发布了 HTTP/2,RFC 编号 7540。
SPDY(读作“SPeeDY”)是Google开发的基于TCP的会话层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。
SPDY并不是一种用于替代HTTP的协议,而是对HTTP协议的增强。新协议的功能包括数据流的多路复用、请求优先级以及HTTP报头压缩。
谷歌表示,引入SPDY协议后,在实验室测试中页面加载速度比原先快64%。
HTTP/2 的制定充分考虑了现今互联网的现状:宽带、移动、不安全,在高度兼容 HTTP/1.1 的同时在性能改善方面做了很大努力,主要的特点有:
- 二进制协议,不再是纯文本
- 可发起多个请求,废弃了 1.1 里的管道
- 使用专用算法压缩头部,减少数据传输量
- 允许服务器主动向客户端推送数据
- 增强了安全性,“事实上”要求加密通信
虽然 HTTP/2 到今天已经五岁,也衍生出了 gRPC 等新协议,但由于 HTTP/1.1 实在是太过经典和强势,目前它的普及率还比较低,大多数网站使用的仍然还是 20 年前的 HTTP/1.1。
HTTP/3
在 HTTP/2 还处于草案之时,Google 又发明了一个新的协议,叫做 QUIC,而且还是相同的“套路”,继续在 Chrome 和自家服务器里试验着“玩”,依托它的庞大用户量和数据量,持续地推动 QUIC 协议成为互联网上的“既成事实”。
2018 年,互联网标准化组织 IETF 提议将“HTTP over QUIC”更名为“HTTP/3”并获得批准,HTTP/3 正式进入了标准化制订阶段,也许两三年后就会正式发布,到时候我们很可能会跳过 HTTP/2 直接进入 HTTP/3。
QUIC(Quick UDP Internet Connection)是谷歌制定的一种基于UDP的低时延的互联网传输层协议。
在2016年11月国际互联网工程任务组(IETF)召开了第一次QUIC工作组会议,受到了业界的广泛关注。
这也意味着QUIC开始了它的标准化过程,成为新一代传输层协议
了解这么多,那到底HTTP是什么呢?
你可能会不假思索、脱口而出:“HTTP 就是超文本传输协议,也就是 HyperText Transfer Protocol。”
回答的也没错,但是太过简单。更准确的回答应该是“HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范”
#关于HttpClient
简介
官网这样说
超文本传输协议(HTTP)可能是当今Internet上使用的最重要的协议。
Web服务,支持网络的设备和网络计算的增长继续将HTTP协议的作用扩展到用户驱动的Web浏览器之外,同时增加了需要HTTP支持的应用程序的数量。
尽管java.net软件包提供了用于通过HTTP访问资源的基本功能,但它并未提供许多应用程序所需的全部灵活性或功能。
HttpClient试图通过提供高效,最新且功能丰富的程序包来实现此空白,以实现最新HTTP标准和建议的客户端。
HttpClient是为扩展而设计的,同时提供了对基本HTTP协议的强大支持,
对于构建HTTP感知的客户端应用程序(例如Web浏览器,Web服务客户端或利用或扩展HTTP协议进行分布式通信的系统)的任何人来说,HttpClient都可能会感兴趣。
URLConnection
,增加了易用性和灵活性,它不仅是客户端发送 HTTP 请求变得容易,而且也方便了开发人员测试接口(基于 HTTP 协议的),即提高了开发的效率,也方便提高代码的健壮性。因此熟练掌握 HttpClient 是很重要的必修内容,掌握 HttpClient 后,相信对于 HTTP 协议的了解会更加深入。- 基于标准、纯净的 Java 语言。实现了 HTTP 1.0 和 HTTP 1.1
- 以可扩展的面向对象的结构实现了 HTTP 全部的方法(GET, POST, PUT, DELETE, HEAD, OPTIONS, and TRACE)。
- 支持 HTTPS 协议。
- 通过 HTTP 代理建立透明的连接。
- 利用 CONNECT 方法通过 HTTP 代理建立隧道的 HTTPS 连接。
- Basic, Digest, NTLMv1, NTLMv2, NTLM2 Session, SNPNEGO/Kerberos 认证方案。
- 插件式的自定义认证方案。
- 便携可靠的套接字工厂使它更容易的使用第三方解决方案。
- 连接管理器支持多线程应用。支持设置最大连接数,同时支持设置每个主机的最大连接数,发现并关闭过期的连接。
- 自动处理 Set-Cookie 中的 Cookie。
- 插件式的自定义 Cookie 策略。
- Request 的输出流可以避免流中内容直接缓冲到 Socket 服务器。
- Response 的输入流可以有效的从 Socket 服务器直接读取相应内容。
- 在 HTTP 1.0 和 HTTP 1.1 中利用 KeepAlive 保持持久连接。
- 直接获取服务器发送的 response code 和 headers。
- 设置连接超时的能力。
- 实验性的支持 HTTP 1.1 response caching。
- 源代码基于 Apache License 可免费获取。
- 创建
HttpClient
对象 - 创建请求方法的实例,并指定请求 URL。如果需要发送 GET 请求,创建
HttpGet
对象;如果需要发送 POST 请求,创建HttpPost
对象 -
如果需要发送请求参数,可调用
HttpGet
、HttpPost
共同的setParams(HttpParams params)
方法来添加请求参数;对于HttpPost
对象而言,也可调用setEntity(HttpEntity entity)
方法来设置请求参数 - 调用
HttpClient
对象的execute(HttpUriRequest request)
发送请求,该方法返回一个HttpResponse
- 调用
HttpResponse
的getAllHeaders()
、getHeaders(String name)
等方法可获取服务器的响应头 - 调用
HttpResponse
的getEntity()
方法可获取HttpEntity
对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容 - 释放连接。无论执行方法是否成功,都必须释放连接
使用用例
pom配置
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.12</version> </dependency>
创建Get请求
@Test
void testGet() {
// 创建 HttpClient 客户端
CloseableHttpClient httpClient = HttpClients.createDefault();
// 创建 HttpGet 请求
HttpGet httpGet = new HttpGet("http://192.168.1.250:15005/dsm-ubm/base-role-info/list");
// 设置长连接
httpGet.setHeader("Connection", "keep-alive");
// 设置认证信息
httpGet.setHeader("Authorization", "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiY3JlYXRlZCI6MTU4ODA2NjM1NTMxNiwiZXhwIjoxNTg4NjcxMTU1fQ.mfroxGQMf_QbHGViEBhQ0hzHoxdNM0TwpGWT64t3LPUl8Sn_ZSBFFKUAt0aKkywM3Lq8245LSXu6BYOptVwYZg");
// 设置代理(模拟浏览器版本)
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
CloseableHttpResponse httpResponse = null;
try {
// 请求并获得响应结果
httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
//打印结果
System.out.println(EntityUtils.toString(httpEntity));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (httpResponse != null) {
try {
httpResponse.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
keep-alive说明
keep-alive:从HTTP/1.1起,浏览器默认都开启了Keep-Alive,保持连接特性,客户端和服务器都能选择随时关闭连接,则请求头中为connection:close。
简单地说,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的TCP连接。
但是Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。
创建Post请求,content-type=application/json
@Test void testPost() { // 创建 HttpClient 客户端 CloseableHttpClient httpClient = HttpClients.createDefault(); // 创建 HttpPost 请求 HttpPost httpPost = new HttpPost("http://192.168.1.250:15005/dsm-ubm/api/getInAreaPatientList"); // 设置长连接 httpPost.setHeader("Connection", "keep-alive"); // 设置认证信息 httpPost.setHeader("Authorization", "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiY3JlYXRlZCI6MTU4ODA2NjM1NTMxNiwiZXhwIjoxNTg4NjcxMTU1fQ.mfroxGQMf_QbHGViEBhQ0hzHoxdNM0TwpGWT64t3LPUl8Sn_ZSBFFKUAt0aKkywM3Lq8245LSXu6BYOptVwYZg"); // 设置代理(模拟浏览器版本) httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"); // 创建 HttpPost 参数 JSONObject jsonObject = new JSONObject(); jsonObject.put("FromTime", ""); jsonObject.put("Ward", "4008"); CloseableHttpResponse httpResponse = null; try { StringEntity entity = new StringEntity(jsonObject.toJSONString(), "utf-8"); entity.setContentType("application/json"); httpPost.setEntity(entity); httpResponse = httpClient.execute(httpPost); HttpEntity httpEntity = httpResponse.getEntity(); //打印结果 System.out.println(EntityUtils.toString(httpEntity)); } catch (IOException e) { e.printStackTrace(); } finally { try { if (httpResponse != null) { httpResponse.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (httpClient != null) { httpClient.close(); } } catch (IOException e) { e.printStackTrace(); } } }
创建Post请求,content-type=application/x-www-form-urlencoded
@Test void testPost() { // 创建 HttpClient 客户端 CloseableHttpClient httpClient = HttpClients.createDefault(); // 创建 HttpPost 请求 HttpPost httpPost = new HttpPost("http://localhost:8080/hello"); // 设置长连接 httpPost.setHeader("Connection", "keep-alive"); // 设置认证信息 httpPost.setHeader("Authorization", "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiY3JlYXRlZCI6MTU4ODA2NjM1NTMxNiwiZXhwIjoxNTg4NjcxMTU1fQ.mfroxGQMf_QbHGViEBhQ0hzHoxdNM0TwpGWT64t3LPUl8Sn_ZSBFFKUAt0aKkywM3Lq8245LSXu6BYOptVwYZg"); // 设置代理(模拟浏览器版本) httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"); // 创建 HttpPost 参数 List<BasicNameValuePair> params = new ArrayList<>(); params.add(new BasicNameValuePair("message", "鹧鸪哨")); CloseableHttpResponse httpResponse = null; try { httpPost.setEntity(new UrlEncodedFormEntity(params, "utf-8")); httpResponse = httpClient.execute(httpPost); HttpEntity httpEntity = httpResponse.getEntity(); //打印结果 System.out.println(EntityUtils.toString(httpEntity)); } catch (IOException e) { e.printStackTrace(); } finally { try { if (httpResponse != null) { httpResponse.close(); } } catch (IOException e) { e.printStackTrace(); } try { if (httpClient != null) { httpClient.close(); } } catch (IOException e) { e.printStackTrace(); } } }
application/json与application/x-www-form-urlencoded区别
application/json和application/x-www-form-urlencoded都是表单数据发送时的编码类型。默认地,表单数据会编码为application/x-www-form-urlencoded。就是说,在发送到服务器之前,所有字符都会进行编码。
application/json,随着json规范的越来越流行,并且浏览器支持程度原来越好,许多开发人员将application/json作为请求content-type,告诉服务器请求的主体内容是json格式的字符串,服务器端会对json字符串进行解析,这种方式的好处就是前端人员不需要关心数据结构的复杂度,只要是标准的json格式就能提交成功,需要封装成对象的话,可以加上@RequestBody注解
application/x-www-form-urlencoded是Jquery的Ajax请求默认方式,这种方式的好处就是浏览器都支持,在请求发送过程中会对数据进行序列化处理,以键值对形式,数据拼接方式为key=value的方式,后台如果使用对象接收的话,可以自动封装成对象
@RequestParam
- 用来处理Content-Type: 为 application/x-www-form-urlencoded编码的内容。(Http协议中,如果不指定Content-Type,则默认传递的参数就是application/x-www-form-urlencoded类型)。
- 在Content-Type: application/x-www-form-urlencoded的请求中, get 方式中queryString的值,和post方式中 body data的值都会被Servlet接受到并转化到Request.getParameter()参数集中,所以@RequestParam可以获取的到。
@RequestBody
- 处理HttpEntity传递过来的数据,一般用来处理非Content-Type: application/x-www-form-urlencoded编码格式的数据。
- GET请求中,因为没有HttpEntity,所以@RequestBody并不适用。
- POST请求中,通过HttpEntity传递的参数,必须要在请求头中声明数据的类型Content-Type,SpringMVC通过使用HandlerAdapter 配置的HttpMessageConverters来解析HttpEntity中的数据,然后绑定到相应的bean上。
@ResponseBody 和 @RequestBody 区别
@ResponseBody是作用在方法上的,@ResponseBody 表示该方法的返回结果直接写入 HTTP response body 中,一般在异步获取数据时使用【也就是AJAX】,在使用 @RequestMapping后,返回值通常解析为跳转路径,但是加上 @ResponseBody 后返回结果不会被解析为跳转路径,而是直接写入 HTTP response body 中。 比如异步获取 json 数据,加上 @ResponseBody 后,会直接返回 json 数据。
@RequestBody 用于读取Request请求的body部分数据,使系统默认的HttpMessageConverter进行解析,然后把相应的数据绑定要返回的对象上,再把HttpMessageConverter返回的对象数据绑定到Controller方法的参数上。
#关于RestTemplate
介绍
RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。RestTemplate 继承自 InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现。是比httpClient更优雅的Restful URL访问。
- RestTemplate是Spring提供的用于访问Rest服务的客户端,
- RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。
- 调用RestTemplate的默认构造函数,RestTemplate对象在底层通过使用java.net包下的实现创建HTTP 请求,
- 可以通过使用ClientHttpRequestFactory指定不同的HTTP请求方式。
- ClientHttpRequestFactory接口主要提供了三种实现方式
- 1、SimpleClientHttpRequestFactory方式,此处生成SimpleBufferingClientHttpRequest,使用HttpURLConnection创建底层的Http请求连接
- 2、HttpComponentsClientHttpRequestFactory方式,此处生成HttpComponentsClientHttpRequest,使用http client来实现网络请求
- 3、OkHttp3ClientHttpRequestFactory方式,此处生成OkHttp3ClientHttpRequest,使用okhttp来实现网络请求
优点
- 并没有重写底层的HTTP请求技术,而是提供配置,可选用OkHttp/HttpClient等
- 在OkHttp/HttpClient之上,封装了请求操作,可以定义Convertor来实现对象到请求body的转换方法,以及返回body到对象的转换方法。
配置
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
//单位为ms
factory.setReadTimeout(5000);
//单位为ms
factory.setConnectTimeout(5000);
return factory;
}
@Primary
@Bean
public ClientHttpRequestFactory httpComponentsClientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
//单位为ms
factory.setReadTimeout(6000);
//单位为ms
factory.setConnectTimeout(6000);
return factory;
}
/*@Bean
public ClientHttpRequestFactory okHttp3ClientHttpRequestFactory() {
OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory();
//单位为ms
factory.setReadTimeout(7000);
//单位为ms
factory.setConnectTimeout(7000);
return factory;
}*/
}
使用
@RunWith(SpringRunner.class) @SpringBootTest public class TestGet { @Autowired private RestTemplate restTemplate; @Test public void testGet() { String url = "http://localhost:8080/welcome?message={1}"; //可以使用map来封装请求参数 Map<String, String> map = new HashMap<>(); map.put("1", "world"); String jsonResult = restTemplate.getForObject(url, String.class, map); System.out.println("result:" + jsonResult); } }
@RunWith(SpringRunner.class) @SpringBootTest public class TestPost { @Autowired private RestTemplate restTemplate; @Test public void testPost() { RequestObj requestObj = RequestObj.builder().id(1) .age(20) .name("鹧鸪哨").build(); String url = "http://localhost:8080/test"; //发起请求 String jsonResult = restTemplate.postForObject(url, requestObj, String.class); System.out.println("result:" + jsonResult); } }
#关于Feign
介绍
Feign被广泛应用在Spring Cloud 的解决方案中,是学习基于Spring Cloud 微服务架构不可或缺的重要组件。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
<!-- feign底层采用的http请求方式 不加则默认使用JDK的HttpURLConnection -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
</dependencies>
Feign配置
@Configuration public class FeignConfig { @Bean Logger.Level feignLoggerLevel() { //记录请求和响应的标头,正文和元数据 return Logger.Level.FULL; } /** * 如果远程接口由于各种问题没有在响应中设置content-type, * 导致FeignClient接收的时候contentType为null,HttpMessageConverterExtractor将其设置为MediaType.APPLICATION_OCTET_STREAM * 此时MessageConverter需要增加MediaType.APPLICATION_OCTET_STREAM支持 */ @Bean public Decoder feignDecoder() { MappingJackson2HttpMessageConverter hmc = new MappingJackson2HttpMessageConverter(customObjectMapper()); List<MediaType> unModifiedMediaTypeList = hmc.getSupportedMediaTypes(); List<MediaType> mediaTypeList = new ArrayList<>(unModifiedMediaTypeList.size() + 1); mediaTypeList.addAll(unModifiedMediaTypeList); mediaTypeList.add(MediaType.APPLICATION_OCTET_STREAM); hmc.setSupportedMediaTypes(mediaTypeList); ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(hmc); return new ResponseEntityDecoder(new SpringDecoder(objectFactory)); }
@Bean public ObjectMapper customObjectMapper() {
//解决LocalDate、LocalDateTime反序列化问题
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
return new ObjectMapper(); } }
启用feign客户端
@SpringBootApplication @EnableFeignClients public class DemoHttpApplication { public static void main(String[] args) { SpringApplication.run(DemoHttpApplication.class, args); } }
定义feign客户端
@FeignClient(name = "test-service", path = "/", url = "http://localhost:8080") public interface TestClient { @GetMapping("/welcome") String welcome(@RequestParam String message); @PostMapping("/test") String test(@RequestBody RequestObj param); }
测试调用
@RunWith(SpringRunner.class) @SpringBootTest public class FeignTest { @Autowired private TestClient testClient; @Test public void testGet() { System.out.println("result:" + testClient.welcome("world")); } @Test public void testPost() { RequestObj requestObj = RequestObj.builder().id(1) .age(20) .name("鹧鸪哨").build(); System.out.println("result:" + testClient.test(requestObj)); } }