RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,默认情况下 RestTemplate 基于 JDK 自带的 HttpURLConnection 建立 HTTP 连接,也可以通过设置 ClientHttpRequestFactory 方法来切换不同的 HTTP 库,如 Apache 的 HttpComponents、Netty、OKHttp。
JDK HttpURLConnection
1.配置属性
#rest 请求配置
rest:
read-timeout: 50000
connect-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
*/
@Configuration
public class CustomRestConfiguration {
@Autowired
private CustomRestProperties restProperties;
/**
* 注入 RestTemplate 对象
*
* @return
*/
@Bean
public RestTemplate restTemplate(SimpleClientHttpRequestFactory requestFactory) {
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
/**
* 设置请求工厂类
*
* @return
*/
@Bean
public 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 {
@Autowired
private RestTemplate restTemplate;
@Test
public 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 中获取,没有才使用默认的字符集编码。
@Override
protected 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 请求。源码内容如下:
@Override
public 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
*/
@Configuration
public class CustomRestConfiguration {
@Autowired
private CustomRestProperties restProperties;
/**
* 注入 RestTemplate 对象
*
* @return
*/
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory requestFactory) {
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
/**
* 设置请求工厂类
*
* @return
*/
@Bean
public 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 对象请求接口。