文章目录
- 1.介绍
- 2.使用
- 2.1 服务器端准备
- 2.2 客户端准备
- 2.3 Get
- 2.3.1 getForObject
- 2.3.2 getForEntity
- 2.4 Post
- 2.4.1 postForObject模拟表单数据提交
- 2.4.2 postForEntity
- 2.4.3 postForLocation
- 2.5 Exchange
- 2.5.1 RESTful风格与HTTP method
- 2.6 异常处理
- 2.6.1 增加异常处理类
- 2.6.2 继承默认实现类DefaultResponseErrorHandler
- 其他
1.介绍
- RestTemplate是执行HTTP请求的同步阻塞式的客户端,它在HTTP客户端库(例如JDK HttpURLConnection,Apache HttpComponents,okHttp等)基础封装了更加简单易用的模板方法API。也就是说RestTemplate是一个封装,底层的实现还是java应用开发中常用的一些HTTP客户端。但是相对于直接使用底层的HTTP客户端库,它的操作更加方便、快捷,能很大程度上提升我们的开发效率。
- RestTemplate作为spring-web项目的一部分,在Spring 3.0版本开始被引入。RestTemplate类通过为HTTP方法(例如GET,POST,PUT,DELETE等)提供重载的方法,提供了一种非常方便的方法访问基于HTTP的Web服务。如果你的Web服务API基于标准的RESTful风格设计,使用效果将更加的完美。
根据Spring官方文档及源码中的介绍,RestTemplate在将来的版本中它可能会被弃用,因为他们已在Spring 5中引入了WebClient作为非阻塞式Reactive HTTP客户端。但是RestTemplate目前在Spring 社区内还是很多项目的“重度依赖”,比如说Spring Cloud。另外,RestTemplate说白了是一个客户端API封装,和服务端相比,非阻塞Reactive 编程的需求并没有那么高。
2.使用
2.1 服务器端准备
在使用之前,我们先构建一个请求服务器,用于http客户端请求
@RestController
@RequestMapping("/api")
@Slf4j
public class PersonController {
@GetMapping("/v2/persons/{pid}")
@ApiOperation(value = "查询用户", notes = "主要是根据ID查询用户",tags = {"person"})
@ApiResponses({
@ApiResponse(code=200,message="成功",response= Book.class),
@ApiResponse(code=401,message="输入参数错误",response=BaseResponse.class),
@ApiResponse(code=500,message="系统内部错误",response=Book.class)
})
public ResponseEntity<Map<String,Object>> getPersonById2(@ApiParam(name = "pid", value = "需要删除的公告ids", required = true) @PathVariable String pid) {
Map<String,Object> map = new HashMap<String,Object>();
//传1,表示正常处理,返回200给请求者
if("1".equals(pid)){
map.put("name", Person.builder().pId("p1").pBirth(new Date()).pName("XIAOMIGN").pScore(1).build());
return new ResponseEntity<Map<String,Object>>(map, HttpStatus.OK);
//传2,表示处理失败,返回非200给请求者
}else if("2".equals(pid)){
map.put("msg","执行失败");
return new ResponseEntity<Map<String,Object>>(map, HttpStatus.LENGTH_REQUIRED);
}
return new ResponseEntity<Map<String,Object>>(map, HttpStatus.OK);
}
}
2.2 客户端准备
1.引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.15.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
</dependencies>
2.增加配置类
@Slf4j
@Configuration
public class RestTemplateConfig {
/** 建立连接的超时时间 */
private static int connectTimeout = 20000;
/** 连接不够用的等待时间 */
private static int requestTimeout = 20000;
/** 每次请求等待返回的超时时间 */
private static int socketTimeout = 30000;
/** 每个主机最大连接数 */
private static int defaultMaxPerRoute = 100;
/** 最大连接数 */
private static int maxTotalConnections = 300;
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate(httpRequestFactory());
//设置异常处理类
restTemplate.setErrorHandler(new MyRestErrorHandler());
return restTemplate;
}
public ClientHttpRequestFactory httpRequestFactory() {
return new HttpComponentsClientHttpRequestFactory(httpClient());
}
public HttpClient httpClient() {
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
//设置整个连接池最大连接数 根据自己的场景决定
connectionManager.setMaxTotal(maxTotalConnections);
//路由是对maxTotal的细分
connectionManager.setDefaultMaxPerRoute(100);
RequestConfig requestConfig = RequestConfig.custom()
//服务器返回数据(response)的时间,超过该时间抛出read timeout
.setSocketTimeout(socketTimeout)
//连接上服务器(握手成功)的时间,超出该时间抛出connect timeout
.setConnectTimeout(connectTimeout)
//从连接池中获取连接的超时时间,超过该时间未拿到可用连接,
// 会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
.setConnectionRequestTimeout(requestTimeout)
.build();
return HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(connectionManager)
.build();
}
}
2.3 Get
2.3.1 getForObject
getForObject()返回值是HTTP协议的响应体
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
RequestCallback requestCallback = this.acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor(responseType, this.getMessageConverters(), this.logger);
return this.execute(url, HttpMethod.GET, requestCallback, responseExtractor, (Map)uriVariables);
}
-
url
: 访问路径 -
responseType
: 响应类型,根据服务接口的返回类型决定 -
uriVariables
:url中参数变量值
1.以String字符串形式接收
@Test
void testSimple() {
String url = "http://jsonplaceholder.typicode.com/posts/1";
String str = restTemplate.getForObject(url, String.class);
System.out.println(str);
}
2.以POJO形式接收
@Test
public void testPoJO() {
String url = "http://jsonplaceholder.typicode.com/posts/1";
PostDTO postDTO = restTemplate.getForObject(url, PostDTO.class);
System.out.println(postDTO.toString());
}
3.以数组形式接收
@Test
public void testArrays() {
String url = "http://jsonplaceholder.typicode.com/posts";
PostDTO[] postDTOs = restTemplate.getForObject(url, PostDTO[].class);
System.out.println("数组长度:" + postDTOs.length);
}
传参方式
- 使用占位符的形式传递参数:
String url = "http://jsonplaceholder.typicode.com/{1}/{2}";
PostDTO postDTO = restTemplate.getForObject(url, PostDTO.class, "posts", 1);
- 另一种使用占位符的形式:
String url = "http://jsonplaceholder.typicode.com/{type}/{id}";
String type = "posts";
int id = 1;
PostDTO postDTO = restTemplate.getForObject(url, PostDTO.class, type, id);
- 也可以使用 map 装载参数:
String url = "http://jsonplaceholder.typicode.com/{type}/{id}";
Map<String,Object> map = new HashMap<>();
map.put("type", "posts");
map.put("id", 1);
PostDTO postDTO = restTemplate.getForObject(url, PostDTO.class, map);
2.3.2 getForEntity
getForEntity()返回的是ResponseEntity,ResponseEntity是对HTTP响应的封装,除了包含响应体,还包含HTTP状态码、contentType、contentLength、Header等信息。
- HttpStatus statusCode = responseEntity.getStatusCode();获取整体的响应状态信息
- int statusCodeValue = responseEntity.getStatusCodeValue(); 获取响应码值
- HttpHeaders headers = responseEntity.getHeaders();获取响应头
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
RequestCallback requestCallback = this.acceptHeaderRequestCallback(responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = this.responseEntityExtractor(responseType);
return (ResponseEntity)nonNull(this.execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables));
}
-
url
: 访问路径 -
responseType
: 响应类型,根据服务接口的返回类型决定 -
uriVariables
:url中参数变量值
String url = "http://jsonplaceholder.typicode.com/posts/5";
ResponseEntity<PostDTO> responseEntity
= restTemplate.getForEntity(url, PostDTO.class);
PostDTO postDTO = responseEntity.getBody(); // 获取响应体
System.out.println("HTTP 响应body:" + postDTO.toString());
2.4 Post
2.4.1 postForObject模拟表单数据提交
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
RequestCallback requestCallback = this.httpEntityCallback(request, responseType);
HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor(responseType, this.getMessageConverters(), this.logger);
return this.execute(url, HttpMethod.POST, requestCallback, responseExtractor, (Map)uriVariables);
}
-
url
: 访问路径 -
request
: 请求参数(包含请求体和请求头) -
responseType
: 响应类型,根据服务接口的返回类型决定 -
uriVariables
:url中参数变量值
@Test
public void testForm() {
// 请求地址
String url = "http://jsonplaceholder.typicode.com/posts";
// 请求头设置,x-www-form-urlencoded格式的数据
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
//提交参数设置
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("title", "文章第二篇");
map.add("body", "第二篇 测试内容");
// 组装请求体
HttpEntity<MultiValueMap<String, String>> request =
new HttpEntity<MultiValueMap<String, String>>(map, headers);
// 发送post请求,并打印结果,以String类型接收响应结果JSON字符串
String result = restTemplate.postForObject(url, request, String.class);
System.out.println(result);
}
也支持url占位符语法,同Get使用
2.4.2 postForEntity
同上
2.4.3 postForLocation
postForLocation的传参的类型、个数、用法基本都和postForObject()或postForEntity()一致。和前两者的唯一区别在于返回值是一个URI。该URI返回值体现的是:用于提交完成数据之后的页面跳转,或数据提交完成之后的下一步数据操作URI。
@Test
public void testURI() {
// 请求地址
String url = "http://jsonplaceholder.typicode.com/posts";
PostDTO postDTO = new PostDTO();
postDTO.setUserId(110);
postDTO.setTitle("发布文章");
postDTO.setBody("发布文章 测试内容");
// 发送post请求,并输出结果
URI uri = restTemplate.postForLocation(url,postDTO);
System.out.println(uri);
}
2.5 Exchange
2.5.1 RESTful风格与HTTP method
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException {
RequestCallback requestCallback = this.httpEntityCallback(requestEntity, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = this.responseEntityExtractor(responseType);
return (ResponseEntity)nonNull(this.execute(url, method, requestCallback, responseExtractor, uriVariables));
}
-
url
: 访问路径 -
method
: 方法方式 -
requestEntity
: 请求参数(包含请求体和请求头) -
responseType
: 响应类型,根据服务接口的返回类型决定 -
uriVariables
:url中参数变量值
以Post举例:
public void test{
String url = "http://jsonplaceholder.typicode.com/posts";
// 请求头设置,x-www-form-urlencoded格式的数据
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
//提交参数设置
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("title", "zimug 发布文章第二篇");
map.add("body", "zimug 发布文章第二篇 测试内容");
// 组装请求体
HttpEntity<MultiValueMap<String, String>> request =
new HttpEntity<MultiValueMap<String, String>>(map, headers);
// 发送post请求,并打印结果,以String类型接收响应结果JSON字符串
ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
}
2.6 异常处理
在使用RestTemplate进行远程接口服务调用的时候,当请求的服务出现异常(超时、服务不存在等情况的时候),响应状态非200
、而是400、500HTTP状态码,就会抛出异常,但不会抛出Body里面的内容信息
,比如服务器端抛出一个403数字的返回码,然后错误信息内容放到了Body里,但我们接收到后,实际是不会打印Body里的信息的,而是一个错误码的异常信息,因此,有两种方式解决
2.6.1 增加异常处理类
1.增加自定义异常以及处理类
public class MyException extends RestClientException {
public MyException(String msg) {
super(msg);
}
}
public class MyRestErrorHandler implements ResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return true;
// int rawStatusCode = response.getRawStatusCode();
// HttpStatus statusCode = HttpStatus.resolve(rawStatusCode);
// return (statusCode != null ? statusCode.isError(): hasError(rawStatusCode));
}
protected boolean hasError(int unknownStatusCode) {
HttpStatus.Series series = HttpStatus.Series.resolve(unknownStatusCode);
return (series == HttpStatus.Series.CLIENT_ERROR || series == HttpStatus.Series.SERVER_ERROR);
}
@Override
public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
throw new MyException("handleError-->> "+convertStreamToString(clientHttpResponse.getBody()));
}
// inputStream 装换为 string
private String convertStreamToString(InputStream is) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
}
2.配置类里增加全局处理
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate(httpRequestFactory());
//设置异常处理类
restTemplate.setErrorHandler(new MyRestErrorHandler());
return restTemplate;
}
3.编写访问
ResponseEntity<String> responseEntity = null;
//使用exchange发送GET请求
if(pid == 1){
responseEntity = restTemplate.exchange("http://localhost:8080/api/v2/persons/1", HttpMethod.GET,
null, String.class);
}else if(pid == 2){
responseEntity = restTemplate.exchange("http://localhost:8080/api/v2/persons/2", HttpMethod.GET,
null, String.class);
}
log.info(responseEntity.toString());
4.当我们传入参数是2的时候,服务器端会传一个4xx的状态码给我们,这个时候,就可以解析出Body里的内容了,如图
2.6.2 继承默认实现类DefaultResponseErrorHandler