一、RestTemplate简介
常见的Http客户端请求工具:
- JDK:HttpURLConnection
- Apache:HttpClient 非常常用
- okHttp非常常用
RestTemplate是一个同步Web Http客户端请求模板工具。
Spring框架做的抽象,本身不具备请求客户端的能力,要选择底层工具。
官方文档地址如下:https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#rest-client-accessRestTemplate
二、RestTemplate的使用:
RestTemplate默认使用HttpURLConnection,可以通过构造方法替换底层的执行引擎也可以通过 setRequestFactory 方法替换,注入等方式替换。常见的执行引擎包括(HttpClient、Netty、okHttp)。参考资料如下:
例如,你如果想切换成Apache的HttpComponents,你可以使用以下建议:
构造函数方式替换引擎
//底层执行引擎HttpURLConnection
RestTemplate template = new RestTemplate();
//底层执行引擎 Apache HttpComponents (HttpClient)
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
//底层执行引擎 OkHttp
RestTemplate template = new RestTemplate(new OkHttpClientHttpRequestFactory());
默认的构造器使用java.net.HttpURLConnection引擎执行请求。你可以使用ClientHttpRequestFactory的实现类来切换不同的HTTP库。内置支持以下内容:
- Apache HttpComponents (HttpClient)
- Netty (网络通信库)
- OkHttp
通过这个请求工厂,我们可以统一设置请求的超时时间,设置代理以及一些其他细节。通过上面代码配置后,我们直接在代码中注入 RestTemplate 就可以使用了。
有时候我们还需要通过 ClientHttpRequestFactory 配置最大链接数,忽略SSL证书等,大家需要的时候可以自己查看代码设置。
RestTemplate提供的方法:
上面的方法我们大致可以分为三组:
- getForObject — optionsForAllow 分为一组,这类方法是常规的 Rest API(GET、POST、DELETE 等)方法调用;
- exchange:接收一个
RequestEntity
参数,可以自己设置 HTTP method,URL,headers 和 body,返回 ResponseEntity; - execute:通过 callback 接口,可以对请求和返回做更加全面的自定义控制。
一般情况下,我们使用第一组和第二组方法就够了。
相关依赖
<!--spring-web依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.4.RELEASE</version>
<scope>compile</scope>
</dependency>
<!--RestTemplate是spring的一个rest客户端,在spring-web这个包下,spring boot的依赖如下-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
实施代码
package com.hayabusa.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestConfig {
//将RestTemplate注入近容器中
@Bean
public RestTemplate restTemplate(){
//默认底层执行 HttpURLTemplate
return new RestTemplate();
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(5000);
factory.setConnectTimeout(15000);
// 设置代理
//factory.setProxy(null);
return factory;
}
}
三、Http 协议 状态码解析
1xx Informational通知
- 100 Continue 通知请求继续
- 101 Switching Protocols
- 102 Processing
2xx Success 请求成功
- 200 OK
- 201 Created
- 202 Accepted
- 203 Non-authoritative Information
- 204 No Content
- 205 Reset Content
- 206 Partial Content
- 207 Multi-Status
- 208 Already Peported
- 226 IM Used
3xx Redirection 重定向
- 300 Multiple Choices
- 301 Moved Permanently 永久迁移
- 302 Found 临时迁移
- 303 See Other
- 304 Not Modified
- 305 Use Proxy
- 307 Temporary Redirect
- 308 Permanent Redirect
4xx Client Error
- 400 Bad Request
- 401 Unanthorized
- 402 Payment Required
- 403 Forbidden
- 404 Not Found 访问的url不存在
- 405 Method Not Allowed
- 406 Not Acceptable
- 407 Proxy Authentication Required
- 408 Request Timeout
5xx Server Error 服务端异常
- 500 Internal Server Error 服务器内部错误
- 501 Not Implemented
- 502 Bad Gateway
- 503 Service Unavailable
- 504 Gateway Timeout
四、RestTemplate常用方法介绍
Http Get请求介绍
RestTemplate有两个核心方法来执行Get请求
- ResetTemplate.getForObject 方法可以获取对象
- RestTemplate.getForEntity 方法不仅可以获取对象,还可以获取Http状态码,请求头等详细信息。
ResetTemplate.getForObject样例程序
@RestController
public class TestRestTemplate {
private final RestTemplate restTemplate;
public TestRestTemplate(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@GetMapping("/getForObject1")
public String getForObject1() {
//远程访问的url
String url = "http://127.0.0.1:8080/test1";
//url代表请求的地址,XXX.class代表返回的类型,paramMap代表要传入的参数
String forObject = restTemplate.getForObject(url, String.class);
String rest = forObject;
System.out.println(rest);
return rest;
}
@GetMapping(value = "/getForObject2", produces = "application/json;charset=utf-8")
public Map getForObject2() {
//远程访问的url
String url = "http://127.0.0.1:8080/test2";
//url代表请求的地址,XXX.class代表返回的类型,paramMap代表要传入的参数
Map forObject = restTemplate.getForObject(url, Map.class);
return forObject;
}
@GetMapping(value = "/getForObject3", produces = "application/json;charset=utf-8")
public Map getForObject3() {
//远程访问的url
String url = "http://127.0.0.1:8080/test3/1/张三";
//url代表请求的地址,XXX.class代表返回的类型,paramMap代表要传入的参数
Map forObject = (Map) restTemplate.getForObject(url, Object.class);
return forObject;
}
}
对应的接收方
@RestController
public class TestController {
@GetMapping("/test1")
public String test1() {
return "张三";
}
@GetMapping("/test2")
public Map test2() {
Map map = new HashMap<>();
map.put("id", 0);
map.put("name", "张三");
map.put("age", 20);
String[] arrays = new String[]{"A", "B", "C", "D", "E"};
map.put("love", arrays);
return map;
}
@GetMapping("/test3/{id}/{name}")
public User test3(@PathVariable("id") Integer id, @PathVariable("name") String name) {
User user = new User(id, name);
return user;
}
}
RestTemplate.getForEntity样例程序
@GetMapping(value = "/getForObject4", produces = "application/json;charset=utf-8")
public Object getForObject4() {
//远程访问的url
String url = "http://127.0.0.1:8080/test4?id={id}&name={name}";
//请求的入参
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("id", 10);
paramMap.put("name", "莉莉");
//url代表请求的地址,XXX.class代表返回的类型,paramMap代表要传入的参数
ResponseEntity<Object> forEntity = restTemplate.getForEntity(url, Object.class, paramMap);
//返回状态码包装类
HttpStatus statusCode = forEntity.getStatusCode();
//返回状态码
int statusCodeValue = forEntity.getStatusCodeValue();
//Http的返回头
HttpHeaders headers = forEntity.getHeaders();
//Http的返回体
Object body = forEntity.getBody();
System.out.println(forEntity);
return body;
}
//=========================接收方=========================
@GetMapping("/test4")
public User test4(Integer id, String name) {
User user = new User(id, name);
return user;
}
RestTemplate之Http Post请求
spring Boot RestTemplate的post方法包含两个核心方法:
1.postForObject
2.postForEntity
SpringBoot RestTemplate的Post和Get方法的区别是Post方法传参Map必须是MultiValueMap
RestTemplate.postForObject样例程序
@GetMapping(value = "/getForObject5", produces = "application/json;charset=utf-8")
public Object getForObject5() {
//远程访问的url
String url = "http://127.0.0.1:8080/test5";
//请求的入参
MultiValueMap map=new LinkedMultiValueMap();
map.add("id", 7);
map.add("name", "花花");
//url代表请求的地址,paramMap代表要传入的参数,XXX.class代表返回的类型
Object rest = restTemplate.postForObject(url, map, Object.class);
return rest;
}
//===========================接收方===========================
@PostMapping("/test5")
public User test5(Integer id,String name) {
User user = new User(id,name);
return user;
}
使用@RequestBody传递参数
@GetMapping(value = "/getForObject6", produces = "application/json;charset=utf-8")
public Object getForObject6() {
//声明请求头
HttpHeaders headers=new HttpHeaders();
//application/json
headers.setContentType(MediaType.APPLICATION_JSON);
//远程访问的url
String url = "http://127.0.0.1:8080/test6";
//使用@RequestBody传递参数使用MultiValueMap会报错
//使用HashMap 会有警告
//好像不使用HttpEntity封装也可以
//请求的入参
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("id", 81);
paramMap.put("name", "花花");
//使用HttpEntity形式包装传递参数
HttpEntity<Map> httpEntity=new HttpEntity<Map>(paramMap,headers);
//url代表请求的地址,paramMap代表要传入的参数,XXX.class代表返回的类型
Object rest = restTemplate.postForObject(url, httpEntity, Object.class);
return rest;
}
//===========================接收方===========================
@PostMapping("/test6")
public User test6(@RequestBody Map map) {
User user = new User((Integer) map.get("id"), (String) map.get("name"));
return user;
}
RestTemplate.postForEntity样例程序
@GetMapping(value = "/getForObject7", produces = "application/json;charset=utf-8")
public Object getForObject7() {
//远程访问的url
String url = "http://127.0.0.1:8080/test7";
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("id", 901);
paramMap.put("name", "花花");
//url代表请求的地址,paramMap代表要传入的参数,XXX.class代表返回的类型
ResponseEntity<Object> objectResponseEntity = restTemplate.postForEntity(url, paramMap, Object.class);
//返回状态码包装类
HttpStatus statusCode = objectResponseEntity.getStatusCode();
//返回状态码
int statusCodeValue = objectResponseEntity.getStatusCodeValue();
//Http的返回头
HttpHeaders headers = objectResponseEntity.getHeaders();
//Http的返回体
Object body = objectResponseEntity.getBody();
return objectResponseEntity.getBody();
}
//===========================接收方===========================
@PostMapping("/test7")
public User test7(@RequestBody Map map) {
User user = new User((Integer) map.get("id"), (String) map.get("name"));
return user;
}
RestTemplate中的Exchange方法
Exchange方法既发送get请求又可以发送post请求。
Exchange方法既可以做getForEntity又可以做postForEntity。
@GetMapping(value = "/getForObject8", produces = "application/json;charset=utf-8")
public Object getForObject8() {
//远程访问的url
String url = "http://127.0.0.1:8080/test8";
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("id", 101);
paramMap.put("name", "花花");
//使用HttpEntity包装传递的参数
HttpEntity httpEntity=new HttpEntity(paramMap);
//url代表请求的地址,HttpMethod.POST请求方式,httpEntity必须使用HttpEntity封装参数,XXX.class代表返回的类型
ResponseEntity<Object> exchange = restTemplate.exchange(url, HttpMethod.POST, httpEntity, Object.class);
return exchange.getBody();
}
//===========================接收方===========================
@PostMapping("/test8")
public User test8(@RequestBody Map map) {
User user = new User((Integer) map.get("id"), (String) map.get("name"));
return user;
}
接口返回值为泛型
接口代码
@GetMapping("/test/getList")
@ResponseBody
public List<BookDto> getList() {
return Arrays.asList(
new BookDto(1, "Spring高手系列"),
new BookDto(2, "SpringMVC系列")
);
}
当接口的返回值为泛型的时候,这种情况比较特殊,使用 RestTemplate 调用上面这个接口,代码如下,需要用到restTemplate.exchange
的方法,这个方法中有个参数是ParameterizedTypeReference
类型,通过这个参数类指定泛型类型
@Test
public void test5() {
RestTemplate restTemplate = new RestTemplate();
//返回值为泛型
String url = "http://localhost:8080/chat16/test/getList";
//若返回结果是泛型类型的,需要使用到exchange方法,
//这个方法中有个参数是ParameterizedTypeReference类型,通过这个参数类指定泛型类型
ResponseEntity<List<BookDto>> responseEntity =
restTemplate.exchange(url,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<BookDto>>() {
});
List<BookDto> bookDtoList = responseEntity.getBody();
System.out.println(bookDtoList);
}
[BookDto{id=1, name='哈雅布撒'}, BookDto{id=2, name='忍者龙剑传'}]
一些其他设置
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);
}
}
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
RestTemplate restTemplate = new RestTemplate(factory);
MyInterceptor myInterceptor = new MyInterceptor();
List<ClientHttpRequestInterceptor> list = new ArrayList<>();
list.add(myInterceptor);
restTemplate.setInterceptors(list);
return restTemplate;
}
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;
}
五、总结
通过 RestTemplate,我们可以非常方便的进行 Rest API 调用。但是在 Spring 5 中已经不再建议使用 RestTemplate,而是建议使用 WebClient。WebClient 是一个支持异步调用的 Client。