RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,默认情况下 RestTemplate 基于 JDK 自带的 HttpURLConnection 建立 HTTP 连接,也可以通过设置 ClientHttpRequestFactory 方法来切换不同的 HTTP 库,如 Apache 的 HttpComponents、Netty、OKHttp。

JDK HttpURLConnection

1.配置属性

  1. #rest 请求配置
  2. rest:
  3. read-timeout: 50000
  4. connect-timeout: 50000
  1. /**
  2. * RestTemplate 属性配置
  3. *
  4. * @author yinjianwei
  5. * @date 2018/08/15
  6. */
  7. @Component
  8. @ConfigurationProperties(prefix = "rest")
  9. public class RestProperties {
  10. /**
  11. * 读超时
  12. */
  13. private Integer readTimeout;
  14. /**
  15. * 连接超时
  16. */
  17. private Integer connectTimeout;
  18. public Integer getReadTimeout() {
  19. return readTimeout;
  20. }
  21. public void setReadTimeout(Integer readTimeout) {
  22. this.readTimeout = readTimeout;
  23. }
  24. public Integer getConnectTimeout() {
  25. return connectTimeout;
  26. }
  27. public void setConnectTimeout(Integer connectTimeout) {
  28. this.connectTimeout = connectTimeout;
  29. }
  30. }

2.自定义配置

  1. /**
  2. * RestTemplate 配置类
  3. *
  4. * @author yinjianwei
  5. * @date 2018/08/14
  6. */
  7. @Configuration
  8. public class CustomRestConfiguration {
  9. @Autowired
  10. private CustomRestProperties restProperties;
  11. /**
  12. * 注入 RestTemplate 对象
  13. *
  14. * @return
  15. */
  16. @Bean
  17. public RestTemplate restTemplate(SimpleClientHttpRequestFactory requestFactory) {
  18. RestTemplate restTemplate = new RestTemplate(requestFactory);
  19. return restTemplate;
  20. }
  21. /**
  22. * 设置请求工厂类
  23. *
  24. * @return
  25. */
  26. @Bean
  27. public SimpleClientHttpRequestFactory requestFactory() {
  28. SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
  29. requestFactory.setReadTimeout(restProperties.getReadTimeout());
  30. requestFactory.setConnectTimeout(restProperties.getConnectTimeout());
  31. return requestFactory;
  32. }
  33. }

3.测试例子

  1. /**
  2. * RestTemplate 测试类
  3. *
  4. * @author yinjianwei
  5. * @date 2018/08/14
  6. */
  7. @RunWith(SpringRunner.class)
  8. @SpringBootTest(classes = Application.class)
  9. public class HttpTest {
  10. @Autowired
  11. private RestTemplate restTemplate;
  12. @Test
  13. public void get() {
  14. String url = "http://localhost:8080/api/get";
  15. String result = restTemplate.getForObject(url, String.class);
  16. System.out.println(result);
  17. }
  18. }

本地起一个不同端口的服务,提供一个 restful 接口供测试使用,目前测试的接口:http://localhost:8080/api/get,返回 JSON 数据。

4.输出结果

  1. {"title":"欢迎"}

5.字符集编码

读取响应数据,转换为字符串类型,看输出结果,没有出现中文乱码问题,查看 RestTemplate 类源码,在构造方法中,添加了如下消息转换器。

  1. public RestTemplate() {
  2. this.messageConverters.add(new ByteArrayHttpMessageConverter());
  3. this.messageConverters.add(new StringHttpMessageConverter());
  4. this.messageConverters.add(new ResourceHttpMessageConverter());
  5. this.messageConverters.add(new SourceHttpMessageConverter<Source>());
  6. this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
  7. if (romePresent) {
  8. this.messageConverters.add(new AtomFeedHttpMessageConverter());
  9. this.messageConverters.add(new RssChannelHttpMessageConverter());
  10. }
  11. if (jackson2XmlPresent) {
  12. this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
  13. }
  14. else if (jaxb2Present) {
  15. this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
  16. }
  17. if (jackson2Present) {
  18. this.messageConverters.add(new MappingJackson2HttpMessageConverter());
  19. }
  20. else if (gsonPresent) {
  21. this.messageConverters.add(new GsonHttpMessageConverter());
  22. }
  23. }

例子中使用的是 StringHttpMessageConverter 作为消息转换器,StringHttpMessageConverter 类默认的字符集编码是 ISO-8859-1,查看 readInternal 和 getContentTypeCharset 方法,字符集编码优先从 response 的 header 中获取,没有才使用默认的字符集编码。

  1. @Override
  2. protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
  3. Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
  4. return StreamUtils.copyToString(inputMessage.getBody(), charset);
  5. }
  6. private Charset getContentTypeCharset(MediaType contentType) {
  7. if (contentType != null && contentType.getCharset() != null) {
  8. return contentType.getCharset();
  9. }
  10. else {
  11. return getDefaultCharset();
  12. }
  13. }

6.设置请求方式

在 SimpleClientHttpRequestFactory 类的 createRequest 方法中,有两种创建 request 的方式,默认是创建基于缓存的 request 请求,还有一种是创建基于流的 request 请求。源码内容如下:

  1. @Override
  2. public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
  3. HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
  4. prepareConnection(connection, httpMethod.name());
  5. if (this.bufferRequestBody) {
  6. return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
  7. }
  8. else {
  9. return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
  10. }
  11. }

通过设置 bufferRequestBody 属性,可以控制创建那种类型的 request,chunkSize 有默认值,也可以在创建 StringHttpMessageConverter 对象的时候设置。

SimpleBufferingClientHttpRequest 的输出流是在本地内存缓存所有 body 数据,再一次性输出到服务端,如果请求数据过大,比如上传大文件,就可能造成内容溢出。 SimpleStreamingClientHttpRequest 是根据设定的固定大小的缓存快,当缓存的 body 数据达到这个块的大小后,就输出到服务端,分批次输出,每个块会复制必需有的头信息。

OKHttp

使用 SimpleClientHttpRequestFactory 不能创建连接池,如果 HTTP 请求比较平凡,会不断的创建 HTTP 连接,释放 HTTP 连接,时间和资源上都是消耗。引入 OKHttp Jar,配置连接池。

1.pom.xml 依赖

  1. <!-- OkHttp -->
  2. <dependency>
  3. <groupId>com.squareup.okhttp3</groupId>
  4. <artifactId>okhttp</artifactId>
  5. <version>${okhttp.version}</version>
  6. </dependency>

2.自定义配置

  1. /**
  2. * RestTemplate 配置类,引入OkHttp
  3. *
  4. * @author yinjianwei
  5. * @date 2018/08/14
  6. */
  7. @Configuration
  8. public class CustomRestConfiguration {
  9. @Autowired
  10. private CustomRestProperties restProperties;
  11. /**
  12. * 注入 RestTemplate 对象
  13. *
  14. * @return
  15. */
  16. @Bean
  17. public RestTemplate restTemplate(ClientHttpRequestFactory requestFactory) {
  18. RestTemplate restTemplate = new RestTemplate(requestFactory);
  19. return restTemplate;
  20. }
  21. /**
  22. * 设置请求工厂类
  23. *
  24. * @return
  25. */
  26. @Bean
  27. public OkHttp3ClientHttpRequestFactory requestFactory() {
  28. // 默认使用了连接池,最大空闲连接数是5,连接的保活时间5分钟
  29. // 源码:{@link okhttp3.OkHttpClient}
  30. OkHttpClient client = new OkHttpClient();
  31. OkHttp3ClientHttpRequestFactory requestFactory = new OkHttp3ClientHttpRequestFactory(client);
  32. requestFactory.setReadTimeout(restProperties.getReadTimeout());
  33. requestFactory.setConnectTimeout(restProperties.getConnectTimeout());
  34. return requestFactory;
  35. }
  36. }

使用和上面的例子一样,都是直接使用 restTemplate 对象请求接口。