文章目录

  • 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

resttemplate 默认keepalive resttemplate no body_spring

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里的内容了,如图

resttemplate 默认keepalive resttemplate no body_客户端_02

2.6.2 继承默认实现类DefaultResponseErrorHandler