当前位置: 代码迷 >> 综合 >> 【Spring】RestTemplate 浅析
  详细解决方案

【Spring】RestTemplate 浅析

热度:63   发布时间:2024-02-05 23:00:30.0

一直在使用RestTemplate 进行服务之间API的调用,只知道RestTemplate restTemplate=new RestTemplate(),然后就可以直接使用post或者get等方法进行请求;直到最近遇到一个问题,在服务之间进行大文件的传输时,总是报出内存溢出的问题,才意识到学习不能浅尝辄止,要明白其中的原理才能够遇到问题时随机应变。本篇文章只是对RestTemplate的源码的一个浅析,不足之处还望读者多包涵。

1、RestTemplate基础

????RestTemplate是Spring中提供的对于Rest服务的请求客户端,使用RestTemplate可以很轻松的实现RestFul风格API的访问。
RestTemplate提供了三种构造函数,如下所示:

	public RestTemplate() {this.messageConverters.add(new ByteArrayHttpMessageConverter());this.messageConverters.add(new StringHttpMessageConverter());this.messageConverters.add(new ResourceHttpMessageConverter(false));this.messageConverters.add(new SourceHttpMessageConverter<>());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());}else if (jsonbPresent) {this.messageConverters.add(new JsonbHttpMessageConverter());}if (jackson2SmilePresent) {this.messageConverters.add(new MappingJackson2SmileHttpMessageConverter());}if (jackson2CborPresent) {this.messageConverters.add(new MappingJackson2CborHttpMessageConverter());}}public RestTemplate(ClientHttpRequestFactory requestFactory) {this();setRequestFactory(requestFactory);}public RestTemplate(List<HttpMessageConverter<?>> messageConverters) {Assert.notEmpty(messageConverters, "At least one HttpMessageConverter required");this.messageConverters.addAll(messageConverters);}

????第一种无参数的构造函数,也是使用最多的;在其中包含有大部分常见的messageConverters,一般的需求直接使用无参构造函数即可满足;
第二种需要ClientHttpRequestFactory 参数的构造函数(无参构造函数中的默认使用的SimpleClientHttpRequestFactory就是实现了该接口),用户可以对实现ClientHttpRequestFactory 接口的类进行自定义的参数设置;
ClientHttpRequestFactory 是一个接口,只包含有一个接口方法:

ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException;

其中实现该接口的主要有以下几个类:
在这里插入图片描述
查看每一个类可以看到基本上apache下的HttpClient、OkHttp3、Netty4(过时)以及java.net.HttpURLConnection都可以实现其接口方法,默认spring使用的是HttpURLConnection;至于其他的怎么选用,可以参考一下:

  • RestTemplate组件:ClientHttpRequestFactory、ClientHttpRequestInterceptor、ResponseExtractor
  • java实现调用http请求的几种常见方式

????第三种需要messageConverters参数的构造函数,当默认的构造函数不能满足需求时,可以继承HttpMessageConverter,自定义相应的Converter来使用。具体的实例可以参考博客:【SpringCloud】RestTemplate访问微信接口时HttpMessageConverter问题

2、使用SimpleClientHttpRequestFactory的注意点

下面为SimpleClientHttpRequestFactory中使用HttpURLConnection来实现createRequest接口方法的源码

public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {private boolean bufferRequestBody = true;public void setBufferRequestBody(boolean bufferRequestBody) {this.bufferRequestBody = bufferRequestBody;}@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默认为true,说明是默认开启缓冲流的方式来构建的ClientHttpRequest ,开启缓冲流以后,数据不会立即的进行读写的操作,而是先放到缓冲流中,当达到缓冲流的最大值时一次性的进行写入操作,这样虽然会提高读写的效率,但是会增加内存的负担,属于拿空间换时间操作;因此这种方式对于进行大文件的传输时就有可能出现内存溢出的问题,解决办法如下:

@Configuration
public class RestTemplateConfig {@Bean(name="uploadRestTemplate")public RestTemplate restTemplate() {SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();requestFactory.setBufferRequestBody(false);  // 禁用缓冲流RestTemplate rest = new RestTemplate(requestFactory);return rest;}
}

3、AbstractClientHttpRequestFactoryWrapper

AbstractClientHttpRequestFactoryWrapper是一个抽象的包装类,对其他的ClientHttpRequestFactory进行包装,其下有两个子类:

  • InterceptingClientHttpRequestFactory
  • BufferingClientHttpRequestFactory

InterceptingClientHttpRequestFactory让被包装的ClientHttpRequestFactory实现类可以具有拦截发送的请求的功能;BufferingClientHttpRequestFactory让被包装的ClientHttpRequestFactory实现类具有缓存作用,接下来介绍InterceptingClientHttpRequestFactory;

RestTemplate继承抽象类InterceptingHttpAccessor,在InterceptingHttpAccessor中有一个很重要的方法:

	/*** Set the request interceptors that this accessor should use.* <p>The interceptors will get sorted according to their order* once the {@link ClientHttpRequestFactory} will be built.* @see #getRequestFactory()* @see AnnotationAwareOrderComparator*/public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {// Take getInterceptors() List as-is when passed in hereif (this.interceptors != interceptors) {this.interceptors.clear();this.interceptors.addAll(interceptors);AnnotationAwareOrderComparator.sort(this.interceptors);}}

可以理解为设置拦截器,其中的拦截器都要实现ClientHttpRequestInterceptor接口才可以,RestTemplste也提供了一些该接口的实现,也可以自己定义,下图中UserInterceptor就是自定义的一个实现:
在这里插入图片描述
在使用RestTemplate的时候如果API需要传递相同的参数,我们可以实现这样的拦截器,从而来减少重复的工作,如下:

@Component
public class UserInterceptor implements ClientHttpRequestInterceptor  {@Overridepublic ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)throws IOException {request.getHeaders().set("userName", "My name is restTemplate!");return execution.execute(request, body);}}
@Configuration
public class RestTemplateConfig {@Autowiredprivate UserInterceptor userInterceptor;@Beanpublic RestTemplate getRestTemplate() {List<ClientHttpRequestInterceptor> interceptors=new ArrayList<>();interceptors.add(userInterceptor);SimpleClientHttpRequestFactory requestFactory=new SimpleClientHttpRequestFactory();requestFactory.setConnectTimeout(1000);RestTemplate restTemplate=new RestTemplate(requestFactory);restTemplate.setInterceptors(interceptors);return restTemplate;}
}

API代码:

	@Autowiredprivate RestTemplate restTemplate;@RequestMapping(value="/sendRestMessage",method=RequestMethod.GET)public String sendRestTemplateMessage() {String url="http://localhost:8089/receiveMessage";ResponseEntity<String> response=restTemplate.getForEntity(url, String.class);System.out.println(restTemplate.getRequestFactory().getClass().toString());return response.getBody();}@RequestMapping(value="/receiveMessage",method=RequestMethod.GET)public String receiveRequestMessage(HttpServletRequest request) {return request.getHeader("userName");}

控制台打印的结果为:org.springframework.http.client.InterceptingClientHttpRequestFactory ,由此可见使用ClientHttpRequestInterceptor接口其实最终还是走的InterceptingClientHttpRequestFactory,将原来的
SimpleClientHttpRequestFactory 进行一次封装,最终的结构为:
在这里插入图片描述
参考博客:https://blog.csdn.net/f641385712/article/details/100713622

  相关解决方案