RestTemplate
是Spring Web中用于和Restful API交互的客户端工具类,使用RestTemplate
我们可以很轻松的发送和处理HTTP请求和响应。
实际开发中,RestTemplate
通常以SpringBean的形式注入其它对象,以便复用这个配置好的Bean。在传统Spring工程中,我们可以通过XML进行配置。
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate"/>
在SpringBoot工程中,我们则可能更倾向于使用JavaConfig配置。
package com.gacfox.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
假设http://localhost:8080/api/v1/getUserById
是一个返回JSON数据的HTTP接口,它的返回内容对应User
类。这里我们可以使用RestTemplate
的getForObject()
方法发起GET请求获取User
对象,下面是一个例子。
package com.gacfox.demo.service;
import com.gacfox.demo.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
@Service("demoService")
public class DemoService {
@Autowired
private RestTemplate restTemplate;
public User getUserById(Long userId) {
UriComponents uriComponents = UriComponentsBuilder
.fromHttpUrl("http://localhost:8080/api/v1/getUserById")
.queryParam("userId", userId)
.build();
String url = uriComponents.encode().toString();
return restTemplate.getForObject(url, User.class);
}
}
代码中,我们使用UriComponentsBuilder
构建了GET请求的URL,然后使用restTemplate.getForObject()
方法发起GET请求,并指定了返回值类型为User
。请求发出时,RestTemplate
会自动添加请求头Accept: application/json
,表示期望返回JSON数据,得到响应数据后,则会自动将JSON数据反序列化为User
对象。
如果服务端未返回200
状态码或解析返回数据时出错,RestTemplate
将抛出对应的异常类型。
发送POST请求也是类似的,下面代码我们使用POST请求发送JSON数据并接收JSON响应。
package com.gacfox.demo.service;
import com.gacfox.demo.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service("demoService")
public class DemoService {
@Autowired
private RestTemplate restTemplate;
public User postUser(User user) {
return restTemplate.postForObject("http://localhost:8080/api/v1/postUser", user, User.class);
}
}
代码中,postForObject()
方法的3个参数分别是HTTP接口地址、请求参数和响应类型。请求参数user
会被自动序列化为JSON数据,响应数据也会被自动反序列化为对应类型。这里RestTemplate
也会对应添加Content-Type
和Accept
请求头。
RestTemplate
为各种HTTP方法都封装了对应的请求方法,不过实际开发中,对于更复杂的业务场景,我们可能更倾向于使用exchange()
方法。exchange()
方法相比前面介绍的getForObject()
和postForObject()
更加底层一些,它能够处理GET、POST等各种请求类型,此外它还能够更精确的控制请求头,此外还支持复杂的泛型参数类型。
package com.gacfox.demo.service;
import com.gacfox.demo.bean.QueryParam;
import com.gacfox.demo.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Service("demoService")
public class DemoService {
@Autowired
private RestTemplate restTemplate;
public List<User> getUserList(QueryParam queryParam) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
HttpEntity<QueryParam> httpEntity = new HttpEntity<>(queryParam, headers);
ResponseEntity<List<User>> responseEntity = restTemplate.exchange(
"http://localhost:8080/api/v1/getUserList",
HttpMethod.POST,
httpEntity,
new ParameterizedTypeReference<List<User>>() {
});
return responseEntity.getBody();
}
}
上面代码比之前更复杂,exchange()
方法需要HttpEntity
类型作为参数,它可以视作HTTP请求的封装,其中包含了请求头、请求JSON体等信息,注意构造HttpEntity
时请求头信息是必要参数的,不过Content-Type
和Accept
请求头仍是默认添加的,此外exchange()
方法的第4个参数是请求体的类型,除了直接传Class
类型,它也支持通过ParameterizedTypeReference
传递泛型参数,这在实际开发中非常有用。
exchange()
方法的返回值是ResponseEntity
,它包含了HTTP响应的详细信息,如响应状态码、响应头、响应体等,这里我们使用getBody()
方法获取响应体,它会被自动反序列化为对应的类型。
此外,和前面的方法类似,当服务端没能返回200状态码或响应解析出错时,RestTemplate
会抛出对应的异常类型。
RestTemplate
默认发送JSON数据,但有些情况如文件上传等我们可能需要发送form-data类型的表单请求,postForObject()
方法是不支持表单请求的,但我们可以通过exchange()
发送手动构造的表单数据实现。
package com.gacfox.demo.service;
import com.gacfox.bean.ApiResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
@Service("demoService")
public class DemoService {
@Autowired
private RestTemplate restTemplate;
public void upload(Long userId, byte[] imageData) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", MediaType.MULTIPART_FORM_DATA_VALUE);
headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
formData.add("userId", String.valueOf(userId));
ByteArrayResource byteArrayResource = new ByteArrayResource(imageData) {
@Override
public String getFilename() {
return "avatar.jpg";
}
};
formData.add("image", byteArrayResource);
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(formData, headers);
restTemplate.exchange(
"http://localhost:8080/api/v1/upload",
HttpMethod.POST,
httpEntity,
new ParameterizedTypeReference<ApiResult<Void>>() {
});
}
}
代码中,我们设置发送数据的Content-Type
为multipart/form-data
,然后构造了一个MultiValueMap
类型的表单数据,其中userId
字段是普通文本字段,image
字段是文件字段,注意image
字段我们使用ByteArrayResource
类型重新封装byte[]
并重写了getFilename()
方法,这样服务端才能正确解析。
RestTemplate
处理文件下载非常简单,我们只需要将返回值接收类型设置为byte[]
,这样就可以二进制数据的形式读取服务端返回的内容。
package com.gacfox.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service("demoService")
public class DemoService {
@Autowired
private RestTemplate restTemplate;
public byte[] download() {
return restTemplate.getForObject("http://localhost:8080/api/v1/download", byte[].class);
}
}
RestTemplate
底层的配置信息可以在创建Bean时通过ClientHttpRequestFactory
指定,下面是JavaConfig的例子,代码中我们配置了RestTemplate
的连接超时和读超时时间。
package com.gacfox.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(15000);
factory.setConnectTimeout(15000);
return factory;
}
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
}
对于XML配置方式也是类似的,这里就不再赘述了。
实际上,RestTemplate
底层默认是基于HttpURLConnection
的,我们也可以将其换成其它的HTTP客户端,如Apache HttpClient、OkHttp等,下面是JavaConfig的例子,代码中我们配置了RestTemplate
使用OkHttpClient
作为底层实现。首先我们引入OkHttp3的依赖。
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.10.0</version>
</dependency>
如下JavaConfig配置了RestTemplate
使用OkHttpClient
作为底层实现。
package com.gacfox.demo.config;
import okhttp3.OkHttpClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.TimeUnit;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectionPool(new okhttp3.ConnectionPool(20, 300, TimeUnit.SECONDS))
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build();
OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory(okHttpClient);
return new RestTemplate(factory);
}
}
代码中我们先创建了OkHttp3的客户端,并为其配置了超时、连接池等信息,随后我们使用该客户端实例化RestTemplate
。
注意:随着时间的发展和OkHttp等HTTP客户端库的迭代更新,RestTemplate
配置底层客户端的API未来情况可能发生变化,不过原理都是类似的,以上代码仅供参考。