前言
本文为深入了解Spring
提供的Rest调用客户端RestTemplate
开山,对它相关的一些组件做讲解。
Tips:请注意区分
RestTemplate
和RedisTemplate
哦~
ClientHttpRequestFactory
它是个函数式接口,用于根据URI和HttpMethod创建出一个ClientHttpRequest来发送请求~
ClientHttpRequest它代表请求的客户端,该接口继承自HttpRequest、HttpOutputMessage,只有一个ClientHttpResponse execute() throws IOException方法。其中Netty、HttpComponents、OkHttp3,HttpUrlConnection对它都有实现~
// @since 3.0 RestTemplate这个体系都是3.0后才有的 @FunctionalInterface public interface ClientHttpRequestFactory { // 返回一个ClientHttpRequest,这样调用其execute()方法就可以发送rest请求了~ ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException; }
它的继承树如下:
可以直观的看到,我们可以使用Apache的HttpClient、OkHttp3、Netty4都可,但这些都需要额外导包,默认情况下Spring使用的是java.net.HttpURLConnection。
HttpClient最新版本:4.5.10
OkHttp最新版本:4.1.1(虽然版本号是4,但是GAV还是3哦:com.squareup.okhttp3)
Netty最新版本:4.1.39.Final(它的5版本可以宣告已死)
Spring4.0是新增了一个对异步支持的AsyncClientHttpRequestFactory(Spring5.0后标记为已废弃):
// 在Spring5.0后被标记为过时了,被org.springframework.http.client.reactive.ClientHttpConnector所取代(但还是可用的嘛) @Deprecated public interface AsyncClientHttpRequestFactory { // AsyncClientHttpRequest#executeAsync()返回的是ListenableFuture<ClientHttpResponse> // 可见它的异步是通过ListenableFuture实现的 AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException; }
使用工厂创建ClientHttpRequest,然后我们发请求就不用关心具体httpClient内部的细节了(可插拔使用二方库、三方库)
SimpleClientHttpRequestFactory
它是Spring内置默认的实现,使用的是JDK内置的java.net.URLConnection作为client客户端。
public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory { private static final int DEFAULT_CHUNK_SIZE = 4096; @Nullable private Proxy proxy; //java.net.Proxy private boolean bufferRequestBody = true; // 默认会缓冲body // URLConnection's connect timeout (in milliseconds). // 若值设置为0,表示永不超时 @see URLConnection#setConnectTimeout(int) private int connectTimeout = -1; // URLConnection#setReadTimeout(int) // 超时规则同上 private int readTimeout = -1; //Set if the underlying URLConnection can be set to 'output streaming' mode. private boolean outputStreaming = true; // 异步的时候需要 @Nullable private AsyncListenableTaskExecutor taskExecutor; ... // 省略所有的set方法 @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { // 打开一个HttpURLConnection HttpURLConnection connection = openConnection(uri.toURL(), this.proxy); // 设置超时时间、请求方法等一些参数到connection prepareConnection(connection, httpMethod.name()); //SimpleBufferingClientHttpRequest的excute方法最终使用的是connection.connect(); // 然后从connection中得到响应码、响应体~~~ if (this.bufferRequestBody) { return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming); } else { return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming); } } // createAsyncRequest()方法略,无非就是在线程池里异步完成请求 ... }
需要注意的是:JDK <1.8 doesn't support getOutputStream with HTTP DELETE
,也就是说如果JDK
的版本低于1.8的话,那么Delete请求是不支持body体的。
Demo Show:
public static void main(String[] args) throws IOException { SimpleClientHttpRequestFactory clientFactory = new SimpleClientHttpRequestFactory(); // ConnectTimeout只有在网络正常的情况下才有效,因此两个一般都设置 clientFactory.setConnectTimeout(5000); //建立连接的超时时间 5秒 clientFactory.setReadTimeout(5000); // 传递数据的超时时间(在网络抖动的情况下,这个参数很有用) ClientHttpRequest client = clientFactory.createRequest(URI.create("https://www.baidu.com"), HttpMethod.GET); // 发送请求 ClientHttpResponse response = client.execute(); System.out.println(response.getStatusCode()); //200 OK System.out.println(response.getStatusText()); // OK System.out.println(response.getHeaders()); // // 返回内容 是个InputStream byte[] bytes = FileCopyUtils.copyToByteArray(response.getBody()); System.out.println(new String(bytes, StandardCharsets.UTF_8)); // 百度首页内容的html }
关于HttpURLConnection的API使用,需注意如下几点:
- HttpURLConnection对象不能直接构造,需要通过URL类中的openConnection()方法来获得
- HttpURLConnection的connect()函数,实际上只是建立了一个与服务器的TCP连接,并没有实际发送HTTP请求。HTTP请求实际上直到我们获取服务器响应数据(如调用getInputStream()、getResponseCode()等方法)时才正式发送出去1. 配置信息都需要在connect()方法执行之前完成
- HttpURLConnection是基于HTTP协议的,其底层通过socket通信实现。如果不设置超时(timeout),在网络异常的情况下,可能会导致程序僵死而不继续往下执行。请务必100%设置
- HTTP正文的内容是通过OutputStream流写入的, 向流中写入的数据不会立即发送到网络,而是存在于内存缓冲区中,待流关闭时,根据写入的内容生成HTTP正文
- 调用getInputStream()方法时,返回一个输入流,用于从中读取服务器对于HTTP请求的返回信息。
- HttpURLConnection.connect()不是必须的。当我们需要返回值时,比如我们使用HttpURLConnection.getInputStream()方法的时候它就会自动发送请求了,所以完全没有必要调用connect()方法了(没必要先建立Tcp嘛~)。
使用哪一个底层http库?
我们知道HttpURLConnection它在功能上是有些不足的(简单的提交参数可以满足)。绝大部分情况下Web站点的网页可能没这么简单,这些页面并不是通过一个简单的URL就可访问的,可能需要用户登录而且具有相应的权限才可访问该页面。在这种情况下,就需要涉及Session、Cookie的处理了,如果打算使用HttpURLConnection来处理这些细节,当然也是可能实现的,只是处理起来难度就大了。
这个时候,Apache开源组织提供了一个HttpClient项目,可以用于发送HTTP请求,接收HTTP响应(包含HttpGet、HttpPost…等各种发送请求的对象)。
它不会缓存服务器的响应,不能执行HTML页面中嵌入的Javascript代码;也不会对页面内容进行任何解析、处理
因此,下面我就让Spring使用HttpClient为示例演示使用三方库:
1、导包
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.10</version> </dependency>
Tips:Requires Apache HttpComponents 4.3 or higher, as of Spring 4.0.
2、案例使用
案例内容仅仅只需把上例第一句话换成使用HttpComponentsClientHttpRequestFactory它的实例,其余都不用变化即可成功看到效果。可以看看这个类它具体做了什么
// @since 3.1 3.1后出现的。 public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean { private HttpClient httpClient; @Nullable private RequestConfig requestConfig; // 这个配置就是可以配置超时等等乱七八糟client属性的类 private boolean bufferRequestBody = true; //=========下面是构造函数们========= public HttpComponentsClientHttpRequestFactory() { // HttpClientBuilder.create().useSystemProperties().build(); // 所有若是这里,配置超时时间可以这么来设置也可: // System.setProperty(”sun.net.client.defaultConnectTimeout”, “5000″); this.httpClient = HttpClients.createSystem(); } // 当然可以把你配置好了的Client扔进来 public HttpComponentsClientHttpRequestFactory(HttpClient httpClient) { this.httpClient = httpClient; } ... // 省略设置超时时间。。。等等属性的一些get/set // 超时信息啥的都是保存在`RequestConfig`里的 @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { HttpClient client = getHttpClient(); // 拿到你指定的client)=(或者系统缺省的) // switch语句逻辑:HttpMethod == GET --> HttpGet HEAD --> HttpHead ... HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri); postProcessHttpRequest(httpRequest); ... } }
实际使用的是HttpClient完成的请求。另外OkHttp3ClientHttpRequestFactory使用的是okhttp3.OkHttpClient发送请求;Netty4ClientHttpRequestFactory使用的是io.netty.channel.EventLoopGroup。此处就不一一例举了
Spring5.0以后,Netty4ClientHttpRequestFactory过期了,建议使用org.springframework.http.client.reactive.ReactorClientHttpConnector代替~
关于HttpURLConnection、HttpClient、OkHttpClient的简单比较:
- HttpURLConnection:
- 优点:JDK内置支持,java的标准类
- 缺点:API不够友好,什么都没封装,用起来太原始,不方便(这其实有时候也算优点,原始就证明好控~)
- HttpClient:
- 优点:功能强大,API友好,使用率够高,几乎成为了实际意义上的标准(相当于对
HttpURLConnection的封装)
- 缺点:性能稍低(比HttpURLConnection低,但4.3后使用连接池进行了改善),API较臃肿,其实Android已经弃用了它~
- OkHttpClient:新一代的Http访问客户端
- 优点:一个专注于性能和易用性的HTTP客户端(节约宽带,Android推荐使用),它设计的首要目标就是高效。提供了最新的 HTTP 协议版本 HTTP/2 和 SPDY 的支持。如果 HTTP/2 和 SPDY 不可用,OkHttp 会使用连接池来复用连接以提高效率
- 暂无。
关于Apache HttpClient,Android5.0之后已经废弃使用它了(API太多,太重),推荐使用更轻量的HttpUrlConnection。(Java开发还是推荐用HttpClient)
OkHttp优点较多:支持SPDY,可以合并多个到同一个主机的请求;OkHttp实现的诸多技术如:连接池,gziping,缓存等;OkHttp 处理了很多网络疑难杂症:会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP;OkHttp是一个Java的HTTP+SPDY客户端开发包,同时也支持Android。默认情况下,OKHttp会自动处理常见的网络问题,像二次连接、SSL的握手问题。支持文件上传、下载、cookie、session、https证书等几乎所有功能。支持取消某个请求
综上所述,不管是Java还是Android,我推荐的自然都是OkHttp(OkHttp使用Okio进行数据传输。都是Square公司自家的,Square公司还出了一个Retrofit库配合OkHttp战斗力翻倍)~~~