RestTemplate原理与使用

1. 缘由

项目中经常需要请求别的服务的HTTP接口,经常用到OkHttp或者HttpClient等,经别人推荐,发现Spring提供的RestTemplate也是挺方便的,对HTTP请求技术框架(如OkHttp/HttpClient)又进行了再一层的封装。如果不了解RestTemplate的原理,用起来总是出问题,此文章会深入的分析RestTemplate的原理,以及探究如何使用。

2. 优点

  • 并没有重写底层的HTTP请求技术,而是提供配置,可选用OkHttp/HttpClient等
  • 在OkHttp/HttpClient之上,封装了请求操作,可以定义Convertor来实现对象到请求body的转换方法,以及返回body到对象的转换方法。

3. 使用

3.1 初始化
//构造HttpClient 
PoolingHttpClientConnectionManager pollingConnectionManager = 
		new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
pollingConnectionManager.setMaxTotal(1000);
pollingConnectionManager.setDefaultMaxPerRoute(1000);
HttpClientBuilder httpClientBuilder = HttpClients.custom();
httpClientBuilder.setConnectionManager(pollingConnectionManager);
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(2, true));
httpClientBuilder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());
List<Header> headers = new ArrayList<>();
headers.add(new BasicHeader("User-Agent", 
	"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));
headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
headers.add(new BasicHeader("Accept-Language", "zh-CN"));
headers.add(new BasicHeader("Connection", "Keep-Alive"));
httpClientBuilder.setDefaultHeaders(headers);
HttpClient httpClient = httpClientBuilder.build();

HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
clientHttpRequestFactory.setConnectTimeout(5 * 1000);
clientHttpRequestFactory.setReadTimeout(5 * 1000);
clientHttpRequestFactory.setConnectionRequestTimeout(5 * 1000);

//定义消息转换器
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
messageConverters.add(new FormHttpMessageConverter());
messageConverters.add(new MappingJackson2HttpMessageConverter());

//构造restTemplate,指定消息转换器/http客户端工厂和错误处理器
restTemplate = new RestTemplate(messageConverters);
restTemplate.setRequestFactory(clientHttpRequestFactory);
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
3.2 简单的GET/POST请求
// GET请求,返回body转换成String
restTemplate.getForObject(url, String.class);
// POST请求,返回body转换成String,requestEntity中可设置请求头和请求body
restTemplate.postForObject(url, requestEntity, String.class);
//exchange方法可以是任意类型的HTTP方法,返回ResponseEntity类型,可获取response的更多信息
restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class).getBody();

4. 深入分析

App RestTemplate UriTemplateHandler ClientHttpRe RequestCallback ClientHttpRequest HttpMessageCon exchange expand 1.将传入的uri 参数扩展到uri中 URI createRequest 2.生成Client HttpRequest ClientHttpRequest doWithRequest 3.匹配合适的Con vertor对数据进 行转换,并且写到ou tputStream中 execute 4.调用client的 实际execute方法 ClientHttpResponse extractData 5.提取返回结果,匹 配合适的Conver tor对数据进行转换 T T App RestTemplate UriTemplateHandler ClientHttpRe RequestCallback ClientHttpRequest HttpMessageCon

可以看出,主要做了四步操作,接下来逐步分析

4.1 根据URL参数扩展URI

uriVariables可以是Object…,也可以是Map<String, ?>,uriTemplateHandler(默认是DefaultUriBuilderFactory)会将参数替换到uri中,如下:

String url = "http://xxx/{id}";
String result = restTemplate.exchange(url, HttpMethod.GET, 
		new HttpEntity<MultiValueMap<String, Object>>(params, headers), String.class,"1").getBody();

expand后的URI是http://xxx/1

4.2 生成ClientHttpRequest

初始化RestTemplates时设置了ClientHttpRequestFactory,此处会获取到ClientHttpRequestFactory来生成ClientHttpRequest。选用怎样的ClientHttpRequestFactory则确定了使用什么样的Http请求技术。

  • 如请求工厂的类型为HttpComponentsClientHttpRequestFactory,则此处生成HttpComponentsClientHttpRequest,使用http client来实现网络请求;
  • 如请求工厂的类型为OkHttp3ClientHttpRequestFactory,则此处生成OkHttp3ClientHttpRequest,使用okhttp来实现网络请求;
4.3 将对象写到请求的outputStream中

观察HttpEntityRequestCallback的doWithRequest方法

RestTemplate HttpEntityRe HttpMessag doWithRequest getMessageConverters 遍历Converter canWrite boolean write RestTemplate HttpEntityRe HttpMessag

获取初始化时设置的Converter,逐个遍历,直至找到符合的Converter,则调用其write方法写到outputStream中,Converter的具体细节将在后面讲到。

4.4 调用请求的执行方法

此步实际是在调用各种Http请求技术,如HttpClient、okhttp的执行方法,具体使用哪种技术,由初始化时传入的ClientHttpRequestFactory确定。

4.5 将response的body转换成对象
RestTemplate HttpMessageCon HttpMessag extractData getMessageConverters 遍历Converter canRead boolean read T T RestTemplate HttpMessageCon HttpMessag

与4.3类似,也是先取出所有converter进行遍历,然后匹配canRead的converter,然后进行数据读取

5. Converter

深入分析中经常出现converter,究竟converter有什么神奇的作用,应该如何设置converter呢,此处对一些常用的converter进行分析。

5.1 StringHttpMessageConverter

StringHttpMessageConverter比较简单,以下是检查canWrite的过程

RestTemplate AbstractHttpMe StringHttpMes MediaType canWrite support boolean getSupportedMediaTypes List<MediaType> 遍历Supporte dMediaType isCompatibleWith boolean boolean RestTemplate AbstractHttpMe StringHttpMes MediaType
public StringHttpMessageConverter(Charset defaultCharset) {
	super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);
}
public boolean supports(Class<?> clazz) {
	return String.class == clazz;
}

从StringHttpMessageConverter的初始化方法和support方法可知,其支持String类型的对象,以及 MediaType.TEXT_PLAIN, MediaType.ALL这两个mediaType,即所有类型的mediaType都支持。
canRead方法也类似,此处不再赘述,任意类型的mediaType以及返回类型为String时,则StringHttpMessageConverter的canRead方法返回为true。
StringHttpMessageConverter的readInternal和writeInternal方法也比较简单,就是以某种指定的charset将字符串写进流里。charset在初始化StringHttpMessageConverter时指定,如果不指定,默认是StandardCharsets.ISO_8859_1。

protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
	Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
	return StreamUtils.copyToString(inputMessage.getBody(), charset);
}
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
	if (this.writeAcceptCharset) {
		outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
	}
	Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
	StreamUtils.copy(str, charset, outputMessage.getBody());
}
5.2 MappingJackson2HttpMessageConverter
  • MappingJackson2HttpMessageConverter的canWrite/canRead方法主要检查两个事:
    (1)Content-Type是否为MediaType.APPLICATION_JSON, new MediaType(“application”, “*+json”)
    (2)对象是否可序列化/反序列化
    如果都为是,则使用MappingJackson2HttpMessageConverter
  • 在write/read方法中,使用jackson的objectMapper将对象按照json格式序列化/反序列,然后写入到流中/从流中读取。
5.3 FormHttpMessageConverter
  • FormHttpMessageConverter的canWrite方法中检查两个事:
    (1)value是否继承MultiValueMap
    (2)Content-Type为这三种任意一种
MediaType.All
MediaType.APPLICATION_FORM_URLENCODED
MediaType.MULTIPART_FORM_DATA
  • 而canRead则检查:
    (1)value是否继承MultiValueMap
    (2)Content-Type为MediaType.APPLICATION_FORM_URLENCODED,源码注释说不支持muiltpart格式的读取。
  • write方法根据是否指定为multipart/form-data,以及map中的value是否存在非value类型的值,来判断是否multipart,如果是multipart则调用writeMultipart方法,否则调用writeForm
public void write(MultiValueMap<String, ?> map, MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException {
	if (!isMultipart(map, contentType)) {
		writeForm((MultiValueMap<String, String>) map, contentType, outputMessage);
	}
	else {
		writeMultipart((MultiValueMap<String, Object>) map, outputMessage);
	}
}

writeForm方法比较简单,将map拼接成key1=value1&key2=value2格式的字符串,然后写到流里。
writeMultipart则比较复杂,其body格式如下

------WebKitFormBoundarywcZ8mD7QHKl1baDP
Content-Disposition: form-data; name="index"
1
------WebKitFormBoundarywcZ8mD7QHKl1baDP
Content-Disposition: form-data; name="file"; filename="xxx.png"
Content-Type: image/png
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
------WebKitFormBoundarywcZ8mD7QHKl1baDP

FormHttpMessageConverter会生成这个boundary,然后逐个part逐个part的写入到流中,最后再写一个boundary来标识结束。
MuiltValueMap中的每一个值会作为一个part,根据值的类型来选择对应的partConverter来进行转换,如果需要指定每个part的header,可以使用类型为HttpEntity的值,实际使用哪个converter由HttpEntity的body的类型决定。
FormHttpMessageConverter默认是支持这三种partConverter,所以当使用multipart/form-data时,multiValueMap中的值必须为String、byte[]、Resource或者HttpEntity类型,但是HttpEntity中的T也必须为String、byte[]、Resource这三种之一。

this.partConverters.add(new ByteArrayHttpMessageConverter());
this.partConverters.add(stringHttpMessageConverter);
this.partConverters.add(new ResourceHttpMessageConverter());

6. 更多用法

6.1 application/json
String useStringConvertor() {		
	String url = "http://xxx";	
	
	MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
	headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
	
	JSONObject params = new JSONObject();
	params.put("name", "小强");
	params.put("phone", "13595688956");
	
	//自行将body转成string,使用StringHttpMessageConverter
	String result = restTemplate.exchange(url, HttpMethod.POST, 
			new HttpEntity<String>(params.toJSONString(), headers), String.class).getBody();
	return result;
}

以上写法,是自行将对象转化为application/json形式的字符串,使用StringHttpMessageConverter写到请求的outputStream中,更优的是直接使用MappingJackson2HttpMessageConverter,无需手动将对象转换成json格式,以免传入错误的格式。

String useJsonConverter() {
	String url = "http://xxx";	

	MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
	headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);

	//此处使用对象/Map/JSONObject都可以
	Map<String, Object> params = new HashMap<>();
	params.put("name", "小强");
	params.put("phone", "13595688956");
	
	//MappingJackson2HttpMessageConverter会使用jackson将对象转成json字符串
	String result = restTemplate.exchange(url, HttpMethod.POST, 
			new HttpEntity<Map<String, Object>>(params, headers), String.class).getBody();
	return result;
}
6.2 multipart/form-data

当需要传文件时经常会使用multipart/form-data格式的body

String useResourceHttpMessageConverter() {
	String url = "http://xxx";	
	
	MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
	headers.add(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE);

	MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
	params.add("file", new FileSystemResource(new File("C:\\xxx.png")));
	
	String result = restTemplate.exchange(url, HttpMethod.POST, 
			new HttpEntity<MultiValueMap<String, Object>>(params, headers), String.class).getBody();
	return result;
}

如上,会先调用到FormHttpMessageConverter的转换方法,然后再使用ResourceHttpMessageConverter的转换方法来实现params到body内容的转换。
也可以使用ByteArrayHttpMessageConverter。

String useByteArrayHttpMessageConverter() throws IOException {
	String url = "http://xxx";	
	
	MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
	headers.add(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE);

	MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
	params.add("file", new ByteArrayResource(FileUtils.readFileToByteArray(new File("C:\\xxx.png"))) {
		@Override
		public String getFilename() {
			return "test.png";
		}
	});
	
	String result = restTemplate.exchange(url, HttpMethod.POST, 
			new HttpEntity<MultiValueMap<String, Object>>(params, headers), String.class).getBody();
	return result;
}

为每一个部分设定header

String useResourceHttpMessageConverter() {
	String url = "http://xxx";	
	
	//设置上传文件的header
	MultiValueMap<String, String> fileHeaders = new LinkedMultiValueMap<>();
	headers.add(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_JPEG_VALUE);
	HttpEntity<Resource> fileEntity= new HttpEntity<Resource>(
		new FileSystemResource(new File("C:\\xxx.png")), fileHeaders);

	//整个请求的header
	MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
	headers.add(HttpHeaders.CONTENT_TYPE, MediaType.MULTIPART_FORM_DATA_VALUE);
	
	//参数使用HttpEntity类型,使得可以指定某个part的header
	MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
	params.add("file", fileEntity);
	
	String result = restTemplate.exchange(url, HttpMethod.POST, 
			new HttpEntity<MultiValueMap<String, Object>>(params, headers), String.class).getBody();
	return result;
}
6.3 下载文件
public byte[] download(String url) {
     try { 
     	HttpHeaders headers = new HttpHeaders();
     	headers.setAccept(Arrays.asList(MediaType.ALL));
     	HttpEntity<byte[]> requestEntity = new HttpEntity<byte[]>(headers);
        return restTemplate.exchange(url, HttpMethod.GET, requestEntity, byte[].class).getBody();	
 }

如上写法使用ByteArrayHttpMessageConverter

  • 9
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RestTemplate 是一个非常常用的 Java HTTP 客户端工具,它可以用来发送 HTTP 请求并获取响应结果。在使用 RestTemplate 的时候,可以通过自定义工具类来简化代码,提高开发效率。 以下是一个简单的 RestTemplate 工具类: ```java public class RestTemplateUtil { private static RestTemplate restTemplate = new RestTemplate(); public static <T> T getForObject(String url, Class<T> responseType, Object... urlVariables) { return restTemplate.getForObject(url, responseType, urlVariables); } public static <T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... urlVariables) { return restTemplate.exchange(url, method, requestEntity, responseType, urlVariables); } public static <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... urlVariables) { return restTemplate.postForEntity(url, request, responseType, urlVariables); } } ``` 这个工具类包含了 RestTemplate 常用的几个方法,可以直接使用。例如: ```java String result = RestTemplateUtil.getForObject("http://example.com", String.class); ``` ```java HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<Object> requestEntity = new HttpEntity<>(requestObject, headers); ResponseEntity<String> responseEntity = RestTemplateUtil.exchange("http://example.com", HttpMethod.POST, requestEntity, String.class); ``` 使用工具类可以避免重复的 RestTemplate 初始化过程,也可以避免代码中出现大量的 RestTemplate 对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值