RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,默认情况下 RestTemplate 基于 JDK 自带的 HttpURLConnection 建立 HTTP 连接,也可以通过设置 ClientHttpRequestFactory 方法来切换不同的 HTTP 库,如 Apache 的 HttpComponents、Netty、OKHttp。
JDK HttpURLConnection
1.配置属性
#rest 请求配置rest:read-timeout: 50000connect-timeout: 50000
/*** RestTemplate 属性配置** @author yinjianwei* @date 2018/08/15*/@Component@ConfigurationProperties(prefix = "rest")public class RestProperties {/*** 读超时*/private Integer readTimeout;/*** 连接超时*/private Integer connectTimeout;public Integer getReadTimeout() {return readTimeout;}public void setReadTimeout(Integer readTimeout) {this.readTimeout = readTimeout;}public Integer getConnectTimeout() {return connectTimeout;}public void setConnectTimeout(Integer connectTimeout) {this.connectTimeout = connectTimeout;}}
2.自定义配置
/*** RestTemplate 配置类** @author yinjianwei* @date 2018/08/14*/@Configurationpublic class CustomRestConfiguration {@Autowiredprivate CustomRestProperties restProperties;/*** 注入 RestTemplate 对象** @return*/@Beanpublic RestTemplate restTemplate(SimpleClientHttpRequestFactory requestFactory) {RestTemplate restTemplate = new RestTemplate(requestFactory);return restTemplate;}/*** 设置请求工厂类** @return*/@Beanpublic SimpleClientHttpRequestFactory requestFactory() {SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();requestFactory.setReadTimeout(restProperties.getReadTimeout());requestFactory.setConnectTimeout(restProperties.getConnectTimeout());return requestFactory;}}
3.测试例子
/*** RestTemplate 测试类** @author yinjianwei* @date 2018/08/14*/@RunWith(SpringRunner.class)@SpringBootTest(classes = Application.class)public class HttpTest {@Autowiredprivate RestTemplate restTemplate;@Testpublic void get() {String url = "http://localhost:8080/api/get";String result = restTemplate.getForObject(url, String.class);System.out.println(result);}}
本地起一个不同端口的服务,提供一个 restful 接口供测试使用,目前测试的接口:http://localhost:8080/api/get,返回 JSON 数据。
4.输出结果
{"title":"欢迎"}
5.字符集编码
读取响应数据,转换为字符串类型,看输出结果,没有出现中文乱码问题,查看 RestTemplate 类源码,在构造方法中,添加了如下消息转换器。
public RestTemplate() {this.messageConverters.add(new ByteArrayHttpMessageConverter());this.messageConverters.add(new StringHttpMessageConverter());this.messageConverters.add(new ResourceHttpMessageConverter());this.messageConverters.add(new SourceHttpMessageConverter<Source>());this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());if (romePresent) {this.messageConverters.add(new AtomFeedHttpMessageConverter());this.messageConverters.add(new RssChannelHttpMessageConverter());}if (jackson2XmlPresent) {this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());}else if (jaxb2Present) {this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());}if (jackson2Present) {this.messageConverters.add(new MappingJackson2HttpMessageConverter());}else if (gsonPresent) {this.messageConverters.add(new GsonHttpMessageConverter());}}
例子中使用的是 StringHttpMessageConverter 作为消息转换器,StringHttpMessageConverter 类默认的字符集编码是 ISO-8859-1,查看 readInternal 和 getContentTypeCharset 方法,字符集编码优先从 response 的 header 中获取,没有才使用默认的字符集编码。
@Overrideprotected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());return StreamUtils.copyToString(inputMessage.getBody(), charset);}private Charset getContentTypeCharset(MediaType contentType) {if (contentType != null && contentType.getCharset() != null) {return contentType.getCharset();}else {return getDefaultCharset();}}
6.设置请求方式
在 SimpleClientHttpRequestFactory 类的 createRequest 方法中,有两种创建 request 的方式,默认是创建基于缓存的 request 请求,还有一种是创建基于流的 request 请求。源码内容如下:
@Overridepublic ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);prepareConnection(connection, httpMethod.name());if (this.bufferRequestBody) {return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);}else {return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);}}
通过设置 bufferRequestBody 属性,可以控制创建那种类型的 request,chunkSize 有默认值,也可以在创建 StringHttpMessageConverter 对象的时候设置。
SimpleBufferingClientHttpRequest 的输出流是在本地内存缓存所有 body 数据,再一次性输出到服务端,如果请求数据过大,比如上传大文件,就可能造成内容溢出。 SimpleStreamingClientHttpRequest 是根据设定的固定大小的缓存快,当缓存的 body 数据达到这个块的大小后,就输出到服务端,分批次输出,每个块会复制必需有的头信息。
OKHttp
使用 SimpleClientHttpRequestFactory 不能创建连接池,如果 HTTP 请求比较平凡,会不断的创建 HTTP 连接,释放 HTTP 连接,时间和资源上都是消耗。引入 OKHttp Jar,配置连接池。
1.pom.xml 依赖
<!-- OkHttp --><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>${okhttp.version}</version></dependency>
2.自定义配置
/*** RestTemplate 配置类,引入OkHttp** @author yinjianwei* @date 2018/08/14*/@Configurationpublic class CustomRestConfiguration {@Autowiredprivate CustomRestProperties restProperties;/*** 注入 RestTemplate 对象** @return*/@Beanpublic RestTemplate restTemplate(ClientHttpRequestFactory requestFactory) {RestTemplate restTemplate = new RestTemplate(requestFactory);return restTemplate;}/*** 设置请求工厂类** @return*/@Beanpublic OkHttp3ClientHttpRequestFactory requestFactory() {// 默认使用了连接池,最大空闲连接数是5,连接的保活时间5分钟// 源码:{@link okhttp3.OkHttpClient}OkHttpClient client = new OkHttpClient();OkHttp3ClientHttpRequestFactory requestFactory = new OkHttp3ClientHttpRequestFactory(client);requestFactory.setReadTimeout(restProperties.getReadTimeout());requestFactory.setConnectTimeout(restProperties.getConnectTimeout());return requestFactory;}}
使用和上面的例子一样,都是直接使用 restTemplate 对象请求接口。
