Spring RestTemplate终极使用归纳总结

前言

第一次看到这个RestTemplate的时候,还是遥远的2017年,记得那年去广西亚信学习,然后从一个项目中看到了RestTemplate,那个时候也就知道怎么用,也就有那么个大概的印象。后来从广西回来后,这几年基本上也就没有过多的写Java代码了,基本就是与C++和Lua为伴;而这些年基本也就不写代码了,更多的是看别人写代码,前段时间评审团队的代码时,发现在调用第三方提供的服务时,都是直接使用的HttpClient调用的,你要是使用HttpClient的话,也是没有任何问题的,关键是你自己封装的工具类还漏洞百出。后来经过评审,由于我们的整个项目都是基于Spring框架的,最后还是决定直接使用RestTemplate+HttpClient的方式调用第三方的服务。

说说RestTemplate

既然要使用RestTemplate,然后自己又好几年没有写Java了,然后又手痒,就写篇文章总结下这个RestTemplate,以及如何使用RestTemplate

RestTemplate是Spring框架提供的一个在应用中调用其他Restful服务的一个工具,它简化了与Http服务的通信方式,统一了Restful的标准,对使用方来说,提供了非常友好的上层API接口。相较于上面说的HttpClient,RestTemplate是一种更优雅的调用Restful服务的方式。所以说,好用才会用。

RestTemplate中包含以下几个部分:

  • HttpMessageConverter:对象转换器;将请求对象转换为具体的数据格式输出,例如:Jaxb2RootElementHttpMessageConverterket提供对xml格式的输入输出支持;可以参见这篇文章,通过实战搞懂HttpMessageConverter

  • ClientHttpRequestFactory:客户端请求工厂接口,可以通过使用ClientHttpRequestFactory指定不同的HTTP请求方式;实现了该接口就都可以与RestTemplate进行集成,后面会细说与不同的框架进行集成;默认是JDK的HttpURLConnection

  • ResponseErrorHandler:异常错误处理;可以参考这篇文章,看下如何自定义异常处理;

  • ClientHttpRequestInterceptor:请求拦截器

以后有机会,我们会再在源码的层面上来对上面的几个部分进行分析。下面来看下RestTemplate类提供的一些主要API:

方法名

描述

getForObject

通过GET请求获得响应结果

getForEntity

通过GET请求获取ResponseEntity对象,包容有状态码,响应头和响应数据

headForHeaders

以HEAD请求资源返回所有响应头信息

postForObject

通过POST请求创建资源,获得响应结果

exchange

更通用版本的请求处理方法,接受一个RequestEntity对象,可以设置路径,请求头,请求信息等,最后返回一个ResponseEntity实体

execute

最通用的执行HTTP请求的方法,上面所有方法都是基于execute的封装,全面控制请求信息,并通过回调接口获得响应数据

下面就开始通过实战的代码来熟悉一下RestTemplate

RestTemplate实战

说了这么多,最重要的还是需要落实在实战上面。下面通过一个基于Spring Boot的测试工程来演示如何使用RestTemplate

getForObject使用示例:

// 不带请求参数
@GetMapping("/getForObject")
public String getForObject() throws JsonProcessingException {
    BookConfigBean bookConfigBean = restTemplate.getForObject("http://127" +
            ".0.0.1:8080/test2", BookConfigBean.class);
    log.info(bookConfigBean.toString());
    return objectMapper.writeValueAsString(bookConfigBean);
}


// 携带请求参数
@GetMapping("/getForObject")
public String getForObject() throws JsonProcessingException {
    Map<String, String> uriVars = new HashMap<>();
    uriVars.put("name", "果冻想公众号");
    uriVars.put("author", "Jelly");
    BookConfigBean bookConfigBean = restTemplate.getForObject("http://127" +
            ".0.0.1:8080/test2?name={name}&author={author}",
            BookConfigBean.class, uriVars);
    log.info(bookConfigBean.toString());
    return objectMapper.writeValueAsString(bookConfigBean);
}

getForEntity使用示例:

@GetMapping("/getForEntity")
public String getForEntity() throws JsonProcessingException {
    Map<String, String> uriVars = new HashMap<>();
    uriVars.put("name", "果冻想公众号");
    uriVars.put("author", "Jelly");
    ResponseEntity<BookConfigBean> responseEntity = restTemplate.getForEntity("http://127" +
                    ".0.0.1:8080/test2?name={name}&author={author}",
            BookConfigBean.class, uriVars);


    HttpStatus statusCode = responseEntity.getStatusCode();
    log.info("statusCode:" + statusCode);


    int httpCode = responseEntity.getStatusCodeValue();
    log.info("statusCodeValue:" + httpCode);


    HttpHeaders headers = responseEntity.getHeaders();
    log.info("headers:" + headers);


    BookConfigBean bookConfigBean = responseEntity.getBody();
    return objectMapper.writeValueAsString(bookConfigBean);
}

postForObject使用示例:

@GetMapping("/postForObject")
public String postForObject() throws JsonProcessingException {
    HttpHeaders headers = new HttpHeaders();
    headers.add("token", "xxxxxxxxxxxxx");
    MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
    map.add("name", "果冻想公众号");
    map.add("author", "Jelly");
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);


    BookConfigBean bookConfigBean = restTemplate.postForObject("http://127" +
            ".0.0.1:8080/test3", request, BookConfigBean.class);
    return objectMapper.writeValueAsString(bookConfigBean);
}

postForEntity使用示例:

@GetMapping("/postForObject")
public String postForObject() throws JsonProcessingException {
    HttpHeaders headers = new HttpHeaders();
    headers.add("token", "xxxxxxxxxxxxx");
    MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
    map.add("name", "果冻想公众号");
    map.add("author", "Jelly");
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);


    ResponseEntity<BookConfigBean> responseEntity =
            this.restTemplate.postForEntity("http://127" +
            ".0.0.1:8080/test3", request, BookConfigBean.class);


    HttpStatus statusCode = responseEntity.getStatusCode();
    log.info("statusCode:" + statusCode);


    int httpCode = responseEntity.getStatusCodeValue();
    log.info("statusCodeValue:" + httpCode);


    headers = responseEntity.getHeaders();
    log.info("headers:" + headers);


    BookConfigBean bookConfigBean = responseEntity.getBody();
    return objectMapper.writeValueAsString(bookConfigBean);
}

exchange使用示例:

// 第一种使用方式
@GetMapping("/exchange")
public String exchange() throws JsonProcessingException {
    HttpHeaders headers = new HttpHeaders();
    headers.add("token", "xxxxxxxxxxxxx");
    HttpEntity<String> request =
            new HttpEntity<String>(null, headers);


    Map<String, String> uriVars = new HashMap<>();
    uriVars.put("name", "果冻想公众号");
    uriVars.put("author", "Jelly");


    ResponseEntity<BookConfigBean> responseEntity =
            restTemplate.exchange("http://127.0.0.1:8080/test2?name={name" +
                    "}&author={author}", HttpMethod.GET, request,
                    BookConfigBean.class, uriVars);
    return objectMapper.writeValueAsString(responseEntity.getBody());
}


// 第二种使用方式
@GetMapping("/exchange")
public String exchange() throws JsonProcessingException {
    HttpHeaders headers = new HttpHeaders();
    headers.add("token", "xxxxxxxxxxxxx");
    MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
    map.add("name", "果冻想公众号");
    map.add("author", "Jelly");
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);


    ResponseEntity<BookConfigBean> responseEntity =
            restTemplate.exchange("http://127.0.0.1:8080/test3",
                    HttpMethod.POST, request,
                    BookConfigBean.class);
    return objectMapper.writeValueAsString(responseEntity.getBody());
}


// 第三种使用方式
@GetMapping("/exchange")
public String exchange() throws JsonProcessingException, URISyntaxException {
    HttpHeaders headers = new HttpHeaders();
    headers.add("token", "xxxxxxxxxxxxx");
    RequestEntity requestEntity = RequestEntity.get(new URI("http://127.0" +
            ".0.1:8080/test2")).headers(headers).build();
    ResponseEntity<BookConfigBean> responseEntity = this.restTemplate.exchange(requestEntity, BookConfigBean.class);
    BookConfigBean bookConfigBean = responseEntity.getBody();
    return objectMapper.writeValueAsString(bookConfigBean);
}


// 第四种使用方式
@GetMapping("/exchange")
public String exchange() throws JsonProcessingException, URISyntaxException {
    HttpHeaders headers = new HttpHeaders();
    headers.add("token", "xxxxxxxxxxxxx");
    MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
    map.add("name", "果冻想公众号");
    map.add("author", "Jelly");


    RequestEntity requestEntity = RequestEntity.post(new URI("http://127" +
            ".0.0.1:8080/test3")).headers(headers).body(map);
    ResponseEntity<BookConfigBean> responseEntity = this.restTemplate.exchange(requestEntity, BookConfigBean.class);
    BookConfigBean bookConfigBean = responseEntity.getBody();
    return objectMapper.writeValueAsString(bookConfigBean);
}

异常处理

前面也简单说了一下异常处理,在Spring中对于RestTemplate提供了一个默认的异常处理,我们在自定义异常处理时建议继承该默认继承类,例如这样:

public class CustomErrorHandler extends DefaultResponseErrorHandler {
    private final Logger log = LoggerFactory.getLogger(CustomErrorHandler.class);


    public void handleError(ClientHttpResponse response) throws IOException {
        // todo
        log.error("自定义异常处理");
    }
}

在构建RestTemplate实例的时候,需要设置异常处理:

@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setErrorHandler(new CustomErrorHandler());
    return restTemplate;
}

RestTemplate与OkHttp集成

上面也说到RestTemplate默认是与JDK的HttpURLConnection进行的集成,但是在实际生产项目中,HttpURLConnection能力不够强大,无法满足我们的要求。而RestTemplate本身又具备与实现了ClientHttpRequestFactory接口的Http Client类库进行集成,在实际工作中,最多的就是RestTemplate与OkHttp集成和RestTemplate与HttpClient集成,这里就重点把这两种集成方式进行总结。

至于OkHttp的牛叉之处,我这里就不再叙述了,因为好用,我这里才进行总结。

要使用OkHttp,首先要添加以下Maven依赖:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.0</version>
</dependency>

接下来,我们对OkHttp3进行配置:

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        return new RestTemplate(factory);
    }


    @Bean
    public ClientHttpRequestFactory okHttp3clientHttpRequestFactory() {
        OkHttpClient httpClient = new OkHttpClient.Builder()
                //.sslSocketFactory(sslSocketFactory(), x509TrustManager())
                .retryOnConnectionFailure(false)
                .connectionPool(pool())
                .connectTimeout(5, TimeUnit.SECONDS)
                .readTimeout(5, TimeUnit.SECONDS)
                .writeTimeout(5, TimeUnit.SECONDS)
                .build();
        return new OkHttp3ClientHttpRequestFactory(httpClient);
    }


    @Bean
    public X509TrustManager x509TrustManager() {
        return new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }
            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        };
    }


    @Bean
    public SSLSocketFactory sslSocketFactory() {
        try {
            //信任任何链接
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{x509TrustManager()}, new SecureRandom());
            return sslContext.getSocketFactory();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        return null;
    }


    @Bean
    public ConnectionPool pool() {
        return new ConnectionPool(200, 5, TimeUnit.MINUTES);
    }
}

RestTemplate与HttpClient集成

对于HttpClient,首先要添加以下Maven依赖:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>

接下来,我们对HttpClient进行配置:

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        return new RestTemplate(factory);
    }


    @Bean
    public ClientHttpRequestFactory httpComponentsClientHttpRequestFactory(){
        //Httpclient连接池,长连接保持30秒
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);


        //设置总连接数
        connectionManager.setMaxTotal(1000);


        //设置同路由的并发数
        connectionManager.setDefaultMaxPerRoute(1000);


        //设置header
        List<Header> headers = new ArrayList<Header>();
        headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.04"));
        headers.add(new BasicHeader("Accept-Encoding", "gzip, deflate"));
        headers.add(new BasicHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3"));
        headers.add(new BasicHeader("Connection", "keep-alive"));


        //创建HttpClient
        HttpClient httpClient = HttpClientBuilder.create()
                .setConnectionManager(connectionManager)
                .setDefaultHeaders(headers)
                .setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)) //设置重试次数
                .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) //设置保持长连接
                .build();


        //创建HttpComponentsClientHttpRequestFactory实例
        HttpComponentsClientHttpRequestFactory requestFactory =
                new HttpComponentsClientHttpRequestFactory(httpClient);


        //设置客户端和服务端建立连接的超时时间
        requestFactory.setConnectTimeout(5000);


        //设置客户端从服务端读取数据的超时时间
        requestFactory.setReadTimeout(5000);


        //设置从连接池获取连接的超时时间,不宜过长
        requestFactory.setConnectionRequestTimeout(200);


        //缓冲请求数据,默认为true。通过POST或者PUT大量发送数据时,建议将此更改为false,以免耗尽内存
        requestFactory.setBufferRequestBody(false);


        return requestFactory;
    }
}

总结

各种找资料,编码验证,用了好久,终于搞定这篇文章,这篇文章里面最大的就是用代码说话,文中的所有代码都经过测试,可以直接复制使用。总结了这篇文章,以后在使用RestTemplate的时候会更加从容和得心应手。

2020年11月7日 于呼和浩特。


人生是个圆,有的人走了一辈子也没有走出命运画出的圆圈,其实,圆上的每一个点都有一条腾飞的切线。

df6262c6568a80e341ea97df5b353ce2.jpeg

玩代码、玩技术

长按识别二维码,关注“果冻想”

如果觉得还不错,可以点个“在看”哦~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章