<导航

Spring的Template使用指南

  一般我们请求接口,都采用Apache Httpclient工具,这个工具稳定,既可以建立长连接,保持不错的性能,而它唯一的不足就是使用起来麻烦多变,并且要很多层判断处理,今天我要谈的就是spring对httpClient的再封装工具类,restTemplate,采用模板模式抽象出来的高效工具。有点类似于jdbcTemplate,今天我们就来一步步揭开它的使用方法。

一、restTemplate简介

RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。

1、restTemplate的类结构

可以看出它继承自HttpAccessor这个统一的处理器,然后再继承自InterceptingHttpAccessor,这个拦截转换器,最终RestTemplate实现了封装httpClient的模板工具类。

二、restTemplate的方法API

  Spring用于同步客户端HTTP访问的中心类。它简化了与HTTP服务器的通信,并执行RESTful原则。它处理HTTP连接,使应用程序代码提供URL,使用可能的模板变量,并提取结果。
  注意:默认情况下,RestTemplate依赖于标准的JDK来建立HTTP连接。你可以切换使用不同的HTTP库,如Apache HttpComponents,Netty和OkHttp通过setRequestFactory属性。 内部模板使用HttpMessageConverter实例将HTTP消息转换为POJO和从POJO转换。主要MIME类型的转换器是默认注册的,但您也可以注册其他转换器通过setMessageConverters。

  以下是http方法和restTempalte方法的比对映射,可以看出restTemplate提供了操作http的方法,其中exchange方法可以用来做任何的请求,一般我们都是用它来封装不同的请求方式。

1、使用GET

1.1 获取JSON字符串

我们可以使用getForEntity()方法:

RestTemplate restTemplate = new RestTemplate();
String fooResourceUrl = "http://localhost:8080/spring-rest/foos";
ResponseEntity<String> response = restTemplate.getForEntity(fooResourceUrl + "/1", String.class);
assertThat(response.getStatusCode(), equalTo(HttpStatus.OK));
  需要说明的是,通过getForEntity()我们可以获取到完整的HTTP response,因此我们可以通过检测状态码来判断请求是否真正执行成功。我们也可以通过getBody()方法获取返回的具体内容,如:
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(response.getBody());
JsonNode name = root.path("name");
assertThat(name.asText(), notNullValue());

1.2 获取POJO对象

我们也可以直接将请求直接映射为一个POJO对象,如:

public class User implements Serializable {
    private long id;
 
    private String name;
    // 这里省略了getters和setters
}

获取User对象:

User user = restTemplate.getForObject(userResourceUrl + "/1", User.class);
assertThat(user.getName(), notNullValue());
assertThat(user.getId(), is(1L));

2. 获取Headers

代码如下:

HttpHeaders httpHeaders = restTemplate.headForHeaders(fooResourceUrl);
assertTrue(httpHeaders.getContentType().includes(MediaType.APPLICATION_JSON));

3. 使用POST

  RestTemplate提供了三个API用来创建资源,它们分别是postForLocation()postForObject()postForEntity()postForLocation()返回新创建资源的URI,postForObject()则返回新创建的资源本身。

3.1 postForObject方法

ClientHttpRequestFactory requestFactory = getClientHttpRequestFactory();
RestTemplate restTemplate = new RestTemplate(requestFactory);
 
HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"));
Foo foo = restTemplate.postForObject(fooResourceUrl, request, Foo.class);
assertThat(foo, notNullValue());
assertThat(foo.getName(), is("bar"));

3.2 postForLocation方法

HttpEntity<Foo> request = new HttpEntity<>(new Foo("bar"));
URI location = restTemplate.postForLocation(fooResourceUrl, request);
assertThat(location, notNullValue());

3.3 exchange方法

RestTemplate还提供了一个更加通用的方法:exchange,下面我们来看看如何使用该方法完成一个POST请求:

RestTemplate restTemplate = new RestTemplate();
HttpEntity<User> request = new HttpEntity<>(new User("CD826"));
ResponseEntity<User> response = restTemplate.exchange(userResourceUrl, HttpMethod.POST, request, User.class);
  
assertThat(response.getStatusCode(), is(HttpStatus.CREATED));
  
User user = response.getBody();
  
assertThat(user, notNullValue());
assertThat(user.getName(), is("CD826"));  

4. 获取允许执行操作列表

optionsForAllow方法可以让我们获取给定URI中允许执行的操作列表:

Set<HttpMethod> optionsForAllow = restTemplate.optionsForAllow(fooResourceUrl);
HttpMethod[] supportedMethods = {HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE};
assertTrue(optionsForAllow.containsAll(Arrays.asList(supportedMethods)));

5. 使用PUT

5.1 简单的PUT

我们先看一个简单的PUT示例,这里需要注意的时该请求并不会有任何返回:

User updatedInstance = new User("newName");
updatedInstance.setId(createResponse.getBody().getId());
String resourceUrl = userResourceUrl + '/' + createResponse.getBody().getId();
HttpEntity<User> requestUpdate = new HttpEntity<>(updatedInstance, headers);
template.exchange(resourceUrl, HttpMethod.PUT, requestUpdate, Void.class);

5.2 带回调的PUT

我们先定义一个回调函数:

RequestCallback requestCallback(final User updatedInstance) {
    return clientHttpRequest -> {
        ObjectMapper mapper = new ObjectMapper();
        mapper.writeValue(clientHttpRequest.getBody(), updatedInstance);
        clientHttpRequest.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        clientHttpRequest.getHeaders().add(HttpHeaders.AUTHORIZATION, "Basic " + getBase64EncodedLogPass());
    };
}

然后,通过POST先创建需要更新的资源:

ResponseEntity<User> response = restTemplate.exchange(userResourceUrl, HttpMethod.POST, request, User.class);
assertThat(response.getStatusCode(), is(HttpStatus.CREATED));

最后,我们使用PUT进行更新:

User updatedInstance = new User("newName");
updatedInstance.setId(response.getBody().getId());
String resourceUrl = userResourceUrl + '/' + response.getBody().getId();
restTemplate.execute(resourceUrl, HttpMethod.PUT, 
                     requestCallback(updatedInstance), 
                     clientHttpResponse -> null);

6. 使用DELETE

示例如下:

String entityUrl = fooResourceUrl + "/" + existingResource.getId();
restTemplate.delete(entityUrl);  

三、restTemplate的配置方法 

  在springboot中的配置,springboot是一款简化传统xml配置式的开发方式,主要采用注解的方式来代替传统繁琐的xml配置,接下来我们就用springboot提供的注解来配置restTemplate:

@Configuration
public class RestTemplateConfig {

    private static final Logger logger= LoggerFactory.getLogger(RestTemplateConfig.class);

    @Bean
    public RestTemplate restTemplate() {
        // 添加内容转换器,使用默认的内容转换器
        RestTemplate restTemplate = new RestTemplate(httpRequestFactory());
        // 设置编码格式为UTF-8
        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
        HttpMessageConverter<?> converterTarget = null;
        for (HttpMessageConverter<?> item : converterList) {
            if (item.getClass() == StringHttpMessageConverter.class) {
                converterTarget = item;
                break;
            }
        }
        if (converterTarget != null) {
            converterList.remove(converterTarget);
        }
        HttpMessageConverter<?> converter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
        converterList.add(1,converter);

        LOGGER.info("-----restTemplate-----初始化完成");
        return restTemplate;
    }

    @Bean
    public ClientHttpRequestFactory httpRequestFactory() {

        return new HttpComponentsClientHttpRequestFactory(httpClient());

    }

    @Bean
    public HttpClient httpClient() {
        // 长连接保持30秒
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
        //设置整个连接池最大连接数 根据自己的场景决定
        connectionManager.setMaxTotal(500);
        //同路由的并发数,路由是对maxTotal的细分
        connectionManager.setDefaultMaxPerRoute(500);

        //requestConfig
        RequestConfig requestConfig = RequestConfig.custom()
                //服务器返回数据(response)的时间,超过该时间抛出read timeout
                .setSocketTimeout(10000)
                //连接上服务器(握手成功)的时间,超出该时间抛出connect timeout
                .setConnectTimeout(5000)
                //从连接池中获取连接的超时时间,超过该时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
                .setConnectionRequestTimeout(500)
                .build();
        //headers
        List<Header> headers = new ArrayList<>();
        headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));
        headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
        headers.add(new BasicHeader("Accept-Language", "zh-CN"));
        headers.add(new BasicHeader("Connection", "Keep-Alive"));
        headers.add(new BasicHeader("Content-type", "application/json;charset=UTF-8"));

        return HttpClientBuilder.create()
                .setDefaultRequestConfig(requestConfig)
                .setConnectionManager(connectionManager)
                .setDefaultHeaders(headers)
                // 保持长连接配置,需要在头添加Keep-Alive
                .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
                //重试次数,默认是3次,没有开启
                .setRetryHandler(new DefaultHttpRequestRetryHandler(2, true))
                .build();
    }
}

  首先解释以下@configuration,它的主要作用就是在spring容器启动的时候,初始化IOC,使用了这个注解,那么该类就会在spring启动的时候,把@Bean注解标识的类进行依赖注入。@Bean理解的话,就好比在配置文件中配置<bean id="xxx">.接下来就是在restTemplate的构造方法中添加httpRequest的工厂,使用连接池来优化http通信,默认使用长连接时间为30秒,再设置路由让http连接定向到指定的IP,然后设置并发数。再就是设置请求配置的超时时间,为了防止请求时间过长而引起资源的过渡浪费。如果在超过设置的timeout还没有数据返回,就直接断开连接。headers是添加默认的请求头,这里设置了传送的格式为json,语言为中-英等等属性。HttpClientBuilder.create设置请求头到HttpClient,然后在设置保持的时间,重试的次数,注入给httpClient进行封装。

在bean中的HttpMessageConverter,就是http信息转换器,它的主要作用就是转换和解析返回来的json数据,restTemplate默认使用jackson来作为底层的解析工具,而其它的比如Gson,fastjson等等第三方开源库放在headers这个list中,如果要使用,可以通过以下代码进行改变:

   this.restTemplate.getMessageConverters().clear();

   final List<HttpMessageConverter<?>> myHttpMessageConverter = new ArrayList<HttpMessageConverter<?>>();
         
    //自己实现的messgeConverter
    HttpMessageConverter<Object> messageConverter = new MyHttpMessageConverter<Object>();
        
    myHttpMessageConverter.add(messageConverter);
        
    this.restTemplate.setMessageConverters(myHttpMessageConverter);

四、总结

  本篇博客讲述了RestTemplate的简介,还有配置方法和使用示例,作为一款非常不错的rest请求工具,屏蔽了复杂的HttpClient的实现细节,向外暴露出简单、易于使用的接口,使得我们的开发工作越来越简单、高效,更多的方法工具可以研究一下restTemplate的具体Api,打开源码,一切都了如指掌。在平时的工作中,应该多发现这种工具类,从而来代替一些传统的工具,对于提升工作效率有着突飞猛进的效果和不可言喻的方便。

扩展:

引自博客:https://www.cnblogs.com/xiaobug/p/11089018.html
springcloud之Feign原理简述
启动时,程序会进行包扫描,扫描所有包下所有@FeignClient注解的类,并将这些类注入到spring的IOC容器中。当定义的Feign中的接口被调用时,通过JDK的动态代理来生成RequestTemplate。
RequestTemplate中包含请求的所有信息,如请求参数,请求URL等。
RequestTemplate声场Request,然后将Request交给client处理,这个client默认是JDK的HTTPUrlConnection,也可以是OKhttp、Apache的HTTPClient等。
最后client封装成LoadBaLanceClient,结合ribbon负载均衡地发起调用。
 

 

参考文章:

https://www.cnblogs.com/wyq178/p/9058030.html

https://www.jianshu.com/p/462790156554

posted @ 2020-11-26 17:11  字节悦动  阅读(1773)  评论(0编辑  收藏  举报