resttemplate重试机制 (resttemplate关闭连接)

在项目中,当我们需要远程调用一个 HTTP 接口时,我们经常会用到 RestTemplate 这个类。这个类是 Spring 框架提供的一个工具类。Spring 官网对它的介绍如下:

RestTemplate : The original Spring REST client with a synchronous, template method API.

从上面的介绍中我们可以知道:RestTemplate 是一个同步的 Rest API 客户端。下面我们就来介绍下 RestTemplate 的常用功能。

1. RestTemplate 方法说明

RestTemplate 提供高度封装的接口,可以让我们非常方便地进行 Rest API 调用。常见的方法如下:

表格:RestTemplate 的方法

resttemplate关闭连接,resttemplate资源释放

上面的方法我们大致可以分为三组:

  1. getForObject --- optionsForAllow 分为一组,这类方法是常规的 Rest API(GET、POST、DELETE 等)方法调用;
  2. exchange:接收一个 RequestEntity 参数,可以自己设置 HTTP method,URL,headers 和 body,返回 ResponseEntity;
  3. execute:通过 callback 接口,可以对请求和返回做更加全面的自定义控制。

一般情况下,我们使用第一组和第二组方法就够了。

2. 创建 RestTemplate

创建对象

@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
    RestTemplate restTemplate = new RestTemplate(factory);
    return restTemplate;}
@Beanpublic ClientHttpRequestFactory simpleClientHttpRequestFactory() {
    SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
    factory.setReadTimeout(5000);
    factory.setConnectTimeout(15000);
// 设置代理    
//factory.setProxy(null);    
return factory;
}

创建 RestTemplate 时需要一个 ClientHttpRequestFactory,通过这个请求工厂,我们可以统一设置请求的超时时间,设置代理以及一些其他细节。通过上面代码配置后,我们直接在代码中注入 RestTemplate 就可以使用了。

2.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*ex.e**cute(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. 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;
}

2. 3. HttpMessageConverter 配置

RestTemplate 也可以配HttpMessageConverter,配置的原理和 Spring MVC 中类似。

3. 接口调用

3.1. 实体定义

public class TestEntity {
    private Integer id;
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "TestEntity{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

3.2. 基础功能调用

@Service
public class RunService {

    @Autowired
    private RestTemplate restTemplate;

    private static String GET_URL = "http://localhost:8080/testGet";
    private static String POST_URL = "http://localhost:8080/testPost";
    private static String POST_PARAM_URL = "http://localhost:8080/testPostParam";
    private static String PUT_URL = "http://localhost:8080/testPut";
    private static String DEL_URL = "http://localhost:8080/testDel";

 
     /**
     * 调用Get接口
     * 实现了三种方式调用
     */
    public void getTestGet() throws URISyntaxException {

        //1、通过getForObject()调用
        TestEntity testEntity1 = this.restTemplate.getForObject(GET_URL, TestEntity.class);
        System.out.println("get testEntity1:"+testEntity1);
//测试结果get testEntity1:TestEntity{id=1, name='get'}

        //2、通过getForEntity()调用
        ResponseEntity<TestEntity> responseEntity1 = this.restTemplate.getForEntity(GET_URL, TestEntity.class);
        HttpStatus statusCode = responseEntity1.getStatusCode();
        HttpHeaders header = responseEntity1.getHeaders();
        TestEntity testEntity2 = responseEntity1.getBody();
        System.out.println("get testEntity2:"+testEntity2);
        System.out.println("get statusCode:"+statusCode);
        System.out.println("get header:"+header);
//测试结果get testEntity2:TestEntity{id=1, name='get'}get statusCode:200get header:{Content-Type=[application/json;charset=UTF-8], Transfer-Encoding=[chunked], Date=[Tue, 24 Apr 2018 07:22:52 GMT]}

        //3、通过exchange()调用
        RequestEntity requestEntity = RequestEntity.get(new URI(GET_URL)).build();
        ResponseEntity<TestEntity> responseEntity2 = this.restTemplate.exchange(requestEntity, TestEntity.class);
        TestEntity testEntity3 = responseEntity2.getBody();
        System.out.println("get testEntity3:"+testEntity3);
//测试结果get testEntity3:TestEntity{id=1, name='get'}

    }

      /**
     * 调用Post接口
     * 实现了三种方式调用
     */
    public void getTestPost() throws URISyntaxException {
        HttpHeaders headers = new HttpHeaders();
        String data = new String();
        HttpEntity<String> formEntity = new HttpEntity<String>(data, headers);

        //1、通过postForObject()调用
        TestEntity testEntity1 = this.restTemplate.postForObject(POST_URL,formEntity, TestEntity.class);
        System.out.println("post testEntity1:"+testEntity1);
//测试结果
post testEntity1:TestEntity{id=1, name='post'}

        //2、通过postForEntity()调用
        ResponseEntity<TestEntity> responseEntity1 = this.restTemplate.postForEntity(POST_URL, formEntity,TestEntity.class);
        HttpStatus statusCode = responseEntity1.getStatusCode();
        HttpHeaders header = responseEntity1.getHeaders();
        TestEntity testEntity2 = responseEntity1.getBody();
        System.out.println("post testEntity2:"+testEntity2);
        System.out.println("post statusCode:"+statusCode);
        System.out.println("post header:"+header);
//测试结果
post testEntity2:TestEntity{id=1, name='post'}
post statusCode:200
post header:{Content-Type=[application/json;charset=UTF-8], Transfer-Encoding=[chunked], Date=[Tue, 24 Apr 2018 07:22:52 GMT]}

        //3、通过exchange()调用
        RequestEntity requestEntity = RequestEntity.post(new URI(POST_URL)).body(formEntity);
        ResponseEntity<TestEntity> responseEntity2 = this.restTemplate.exchange(requestEntity, TestEntity.class);
        TestEntity testEntity3 = responseEntity2.getBody();
        System.out.println("post testEntity3:"+testEntity3);
getTestPut succ//测试结果
post testEntity3:TestEntity{id=1, name='post'}

    }


    /**
     * 调用Post接口,并传递了参数
     */
    public void getTestPostParam(){
        HttpHeaders headers = new HttpHeaders();
        MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
        map.add("id", "100");
        map.add("name", "getTestPostParam");
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
        String data = restTemplate.postForObject(POST_PARAM_URL,request,String.class);
        System.out.println("getTestPostParam data: "+ data);
        System.out.println("getTestPostParam succ");
//测试结果
getTestPostParam data: post succ
    }

    /**
     * 调用Put接口
     */
    public void getTestPut(){

        MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
        map.add("id", "101");
        map.add("name", "getTestPut");
        restTemplate.put(PUT_URL,map);
        System.out.println("getTestPut succ");
//测试结果
getTestPostParam succ

    }

    /**
     * 调用Del接口
     */
    public void getTestDel(){

        MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
        map.add("id", "101");
        map.add("name", "getTestPut");
        restTemplate.delete(DEL_URL,map);
//测试结果
getTestPut succ

    }
}

3.3. 添加 Header 和 Cookie

有时候,我们需要在请求中的 Head 中添加值或者将某些值通过 cookie 传给服务端,那么上面这种调用形式就不太满足要求了。

riComponents 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.4. 文件上传

3.4.1. 简单文件上传

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;
    }

3.4.2. 文件接收

上面的代码中上传了两个本地图片,通过下面代码可以顺利接收。

@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;
    }

3.4.3. 自定义文件上传

上面的上传代码中,上传文件的类必须使用 FileSystemResource。有时我们会碰到这种情况:文件我们会从文件服务*载下**到内存中一个 InputStream 的形式存在,那此时在使用 FileSystemResource 就不行了。

当然,我们使用讨巧一点的办法也是可以的:先将*载下**下来的 InputStream 保存到本地,然后再读取到 FileSystemResource,上传后再删除本地临时文件。

但是总觉得这个方法不够完美。最后发现有个同事已经写了相关的实现。这边就直接拿来用了。

public class InMemoryResource extends ByteArrayResource {
    private final String filename;
    private final long lastModified;

    public InMemoryResource(String filename, String description, byte[] content, long lastModified) {
        super(content, description);
        this.lastModified = lastModified;
        this.filename = filename;
    }

    @Override
    public long lastModified() throws IOException {
        return this.lastModified;
    }

    @Override
    public String getFilename() {
        return this.filename;
    }}

调整后的上传

@PostMapping("/m3")
    public Object m3(@RequestBody JSONObject params) throws Exception{

        final String url = "http://localhost:8888/hello/m3";
        // 设置请求头        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        // 设置请求体,注意是 LinkedMultiValueMap        // 下面两个流从文件服务*载下**,这边省略(注意最后关闭流)        InputStream fis1 = 
        InputStream fis2 = 

        InMemoryResource resource1 = new InMemoryResource("file1.jpg","description1", FileCopyUtils.copyToByteArray(fis1), System.currentTimeMillis());
        InMemoryResource resource2 = new InMemoryResource("file2.jpg","description2", FileCopyUtils.copyToByteArray(fis2), System.currentTimeMillis());
        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;
    }

4. 简单总结

通过 RestTemplate,我们可以非常方便的进行 Rest API 调用。但是在 Spring 5 中已经不再建议使用 RestTemplate,而是建议使用 WebClient。WebClient 是一个支持异步调用的 Client。所以喜欢研究新东西的同学可以开始研究下新东西了。