引言
现如今的 IT 项目,由服务端向外发起网络请求的场景,基本上处处可见!
传统情况下,在服务端代码里访问 http 服务时,一般会使用 JDK 的 HttpURLConnection 或者 Apache 的 HttpClient,不过这种方法使用起来太过繁琐,而且 api 使用起来非常的复杂,还得操心资源回收。
以下载文件为例,通过 Apache 的 HttpClient方式进行下载文件,会很复杂
其实Spring已经为我们提供了一种简单便捷的模板类来进行操作,它就是RestTemplate
RestTemplate是一个执行HTTP请求的同步阻塞式工具类,它仅仅只是在 HTTP 客户端库(例如 JDK HttpURLConnection,Apache HttpComponents,okHttp 等)基础上,封装了更加简单易用的模板方法 API,方便程序员利用已提供的模板方法发起网络请求和处理,能很大程度上提升我们的开发效率
环境配置
在项目中,当我们需要远程调用一个 HTTP 接口时,我们经常会用到 RestTemplate 这个类。这个类是 Spring 框架提供的一个工具类。Spring 官网对它的介绍如下:

从上面的介绍中我们可以知道:RestTemplate 是一个同步的 Rest API 客户端。下面我们就来介绍下 RestTemplate 的常用功能。
2.非Spring环境下使用RestTemplate
如果当前项目不是Spring项目,加入spring-web包,即可引入RestTemplate类
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.2.6.RELEASE</version> </dependency>
编写一个单元测试类,使用RestTemplate发送一个GET请求,看看程序运行是否正常
@Test
public void simpleTest() {
RestTemplate restTemplate = new RestTemplate();
String url = "http://jsonplaceholder.typicode.com/posts/1";
String str = restTemplate.getForObject(url, String.class);
System.out.println(str);
}
3.Spring 环境下使用 RestTemplate
如果当前项目是SpringBoot,添加如下依赖接口!
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
但是Spring并没有将其加入SpringBean容器中,需要我们手动加入,因为我们首先创建一个Springboot配置类,再在配置类中将我们的RestTemlate注册到Bean容器中
例如:将RestTemplate配置初始化为一个Bean
@Configuration
public class RestTemplateConfig {
/**
* 没有实例化RestTemplate时,初始化RestTemplate
* @return
*/
@ConditionalOnMissingBean(RestTemplate.class)
@Bean
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}
}
注意,这种初始化方法,是使用了JDK自带的HttpURLConnection作为底层HTTP客户端实现。
当然,我们还可以修改RestTemplate默认的客户端,例如将其改成HttpClient客户端,方式如下:
@Configuration
public class RestTemplateConfig {
@ConditionalOnMissingBean(RestTemplate.class)
@Bean
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
return restTemplate;
}
/**
* 使用HttpClient作为底层客户端
* @return
*/
private ClientHttpRequestFactory getClientHttpRequestFactory() {
int timeout = 5000;
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout)
.setSocketTimeout(timeout)
.build();
CloseableHttpClient client = HttpClientBuilder
.create()
.setDefaultRequestConfig(config)
.build();
return new HttpComponentsClientHttpRequestFactory(client);
}
}
在需要使用RestTemplate的位置,注入并使用即可!
@Autowired private RestTemplate restTemplate;
从开发人员的反馈,和网上的各种HTTP客户端性能以及易用程度评测来看,OkHttp 优于 Apache的HttpClient、Apache的HttpClient优于HttpURLConnection。
因此,我们还可以通过如下方式,将底层的http客户端换成OkHttp
/**
* 使用OkHttpClient作为底层客户端
* @return
*/
private ClientHttpRequestFactory getClientHttpRequestFactory(){
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.build();
return new OkHttp3ClientHttpRequestFactory(okHttpClient);
}
restTemplate简介
1.restTemplate的类结构

可以看出它继承自HttpAccessor这个统一的处理器,然后再继承自InterceptingHttpAccessor,这个拦截转换器,最终RestTemplate实现了封装httpClient的模板工具类
2.restTemplate的方法
Spring用于同步客户端HTTP访问的中心类。它简化了与HTTP服务器的通信,并执行RESTful原则。它处理HTTP连接,使应用程序代码提供URL,使用可能的模板变量,并提取结果。
注意:默认情况下,RestTemplate依赖于标准的JDK来建立HTTP连接。你可以切换使用不同的HTTP库,如Apache HttpComponents,Netty和OkHttp通过setRequestFactory属性。 内部模板使用HttpMessageConverter实例将HTTP消息转换为POJO和从POJO转换。主要MIME类型的转换器是默认注册的,但您也可以注册其他转换器通过setMessageConverters
以下是http方法和restTempalte方法的比对映射,可以看出restTemplate提供了操作http的方法,其中exchange方法可以用来做任何的请求,一般我们都是用它来封装不同的请求方式。


上面的方法我们大致可以分为三组:
- getForObject — optionsForAllow 分为一组,这类方法是常规的 Rest API(GET、POST、DELETE 等)方法调用;
- exchange:接收一个
RequestEntity参数,可以自己设置 HTTP method,URL,headers 和 body,返回 ResponseEntity; - execute:通过 callback 接口,可以对请求和返回做更加全面的自定义控制。
3.创建 RestTemplate
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
RestTemplate restTemplate = new RestTemplate(factory);
return restTemplate;
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(5000);
factory.setConnectTimeout(15000);
// 设置代理
//factory.setProxy(null);
return factory;
}
创建 RestTemplate 时需要一个 ClientHttpRequestFactory,通过这个请求工厂,我们可以统一设置请求的超时时间,设置代理以及一些其他细节。通过上面代码配置后,我们直接在代码中注入 RestTemplate 就可以使用了。
有时候我们还需要通过 ClientHttpRequestFactory 配置最大链接数,忽略SSL证书等,大家需要的时候可以自己查看代码设置。
接口调用
1. 普通接口调用
Map<String, String> vars = Collections.singletonMap("hotel", "42");
// 通过 GET 方式调用,返回一个 String 值,还可以给 URL 变量设置值(也可通过 uriTemplateHandler 这个属性自定义)
String result = restTemplate.getForObject(
"https://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
String url = "http://127.0.0.1:8080/hello";
JSONObject param = new JSONObject();
//restTemplate 会根据 params 的具体类型,调用合适的 HttpMessageConvert 将请求参数写到请求体 body 中,并在请求头中添加合适的 content-type;
// 也会根据 responseType 的类型(本列子中是 JSONObject),设置 head 中的 accept 字段,当响应返回的时候再调用合适的 HttpMessageConvert 进行响应转换
ResponseEntity<JSONObject> responseEntity=restTemplate.postForEntity(url,params,JSONObject.class);
int statusCodeValue = responseEntity.getStatusCodeValue();
HttpHeaders headers = responseEntity.getHeaders();
JSONObject body = responseEntity.getBody();
2. 添加 Header 和 Cookie
有时候,我们需要在请求中的 Head 中添加值或者将某些值通过 cookie 传给服务端,那么上面这种调用形式就不太满足要求了。
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl("127.0.0.1:8080").
path("/test").build(true);
URI uri = uriComponents.toUri();
RequestEntity<JSONObject> requestEntity = RequestEntity.post(uri).
// 添加 cookie(这边有个问题,假如我们要设置 cookie 的生命周期,作用域等参数我们要怎么操作)
header(HttpHeaders.COOKIE,"key1=value1").
// 添加 header
header(("MyRequestHeader", "MyValue")
accept(MediaType.APPLICATION_JSON).
contentType(MediaType.APPLICATION_JSON).
body(requestParam);
ResponseEntity<JSONObject> responseEntity = restTemplate.exchange(requestEntity,JSONObject.class);
// 响应结果
JSONObject responseEntityBody = responseEntity.getBody();
3. 文件上传
上面两个列子基本能覆盖我们平时开发的大多数功能了。这边再讲个文件上传的列子(RestTemplate 功能还是蛮全的)。
public Object uplaod(@RequestBody JSONObject params) throws Exception{
final String url = "http://localhost:8888/hello/m3";
// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
// 设置请求体,注意是 LinkedMultiValueMap
FileSystemResource resource1 = new FileSystemResource("D:\\dir1\\ss\\pic1.jpg");
FileSystemResource resource2 = new FileSystemResource("D:\\dir1\\ss\\pic2.jpg");
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("file", resource1);
form.add("file", resource2);
form.add("param1","value1");
HttpEntity<MultiValueMap<String, Object>> files = new HttpEntity<>(form, headers);
JSONObject s = restTemplate.postForObject(url, files, JSONObject.class);
return s;
}
上面的代码中上传了两个本地图片,通过下面代码可以顺利接收。
@RequestMapping("/m3")
public Object fileUpload(@RequestParam("file") MultipartFile[] files, HttpServletRequest request) throws Exception {
// 携带的其他参数可以使用 getParameter 方法接收
String param1 = request.getParameter("param1");
Response response = new Response();
if (files == null) {
response.failure("文件上传错误, 服务端未拿到上传的文件!");
return response;
}
for (MultipartFile file : files) {
if (!file.isEmpty() && file.getSize() > 0) {
String fileName = file.getOriginalFilename();
// 参考 FileCopyUtils 这个工具类
file.transferTo(new File("D:\\" + fileName));
logger.info("文件:{} 上传成功...",fileName);
}
}
response.success("文件上传成功");
return response;
}
4. 文件下载
private InputStream downLoadVideoFromVod(String url) throws Exception {
byte[] bytes = restTemplate.getForObject(url, byte[].class);
return new ByteArrayInputStream(bytes);
}
一些其他设置
1. 拦截器配置
RestTemplate 也可以设置拦截器做一些统一处理。这个功能感觉和 Spring MVC 的拦截器类似。配置也很简单:
class MyInterceptor implements ClientHttpRequestInterceptor{
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
logger.info("enter interceptor...");
return execution.execute(request,body);
}
}
2. ErrorHandler 配置
ErrorHandler 用来对调用错误对统一处理。
public class MyResponseErrorHandler extends DefaultResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return super.hasError(response);
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode());
if (statusCode == null) {
throw new UnknownHttpStatusCodeException(response.getRawStatusCode(), response.getStatusText(),
response.getHeaders(), getResponseBody(response), getCharset(response));
}
handleError(response, statusCode);
}
@Override
protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
switch (statusCode.series()) {
case CLIENT_ERROR:
HttpClientErrorException exp1 = new HttpClientErrorException(statusCode, response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response));
logger.error("客户端调用异常",exp1);
throw exp1;
case SERVER_ERROR:
HttpServerErrorException exp2 = new HttpServerErrorException(statusCode, response.getStatusText(),
response.getHeaders(), getResponseBody(response), getCharset(response));
logger.error("服务端调用异常",exp2);
throw exp2;
default:
UnknownHttpStatusCodeException exp3 = new UnknownHttpStatusCodeException(statusCode.value(), response.getStatusText(),
response.getHeaders(), getResponseBody(response), getCharset(response));
logger.error("网络调用未知异常");
throw exp3;
}
}
}
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
RestTemplate restTemplate = new RestTemplate(factory);
MyResponseErrorHandler errorHandler = new MyResponseErrorHandler();
restTemplate.setErrorHandler(errorHandler);
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
// 通过下面代码可以添加新的 HttpMessageConverter
//messageConverters.add(new );
return restTemplate;
}
3. HttpMessageConverter 配置
RestTemplate 也可以配置 HttpMessageConverter,配置的原理和 Spring MVC 中类似。