关于Spring的RestTemplate的使用

这是我参与11月更文挑战的第21天,活动详情查看:11月更文挑战

现在微服务项目中, 各个服务之间的互相请求调用, 越来越频繁, 所以出现了很多Http请求工具, 而Spring提供的 RestTemplate,则是比较友好的封装集成了一些常见的Http请求工具,可以根据不同的使用场景,灵活的切换

1 RestTemplate的简介

RestTemplate是Spring3.0推出的, 官方给的解释是: 同步客户端执行HTTP请求,暴露出一个简单,模板的基于底层HTTP客户端库的方法API, 像JDK自带的HttpURLConnection, Apache HttpComponents和其他.

classDiagram
      HttpAccessor <|-- InterceptingHttpAccessor
      InterceptingHttpAccessor <|-- RestTemplate
      RestOperations <|.. RestTemplate
      class HttpAccessor{
          -ClientHttpRequestFactory requestFactory
          -List<ClientHttpRequestInitializer> clientHttpRequestInitializers
      }
      class InterceptingHttpAccessor{
          -ClientHttpRequestFactory requestFactory
          -List<ClientHttpRequestInitializer> clientHttpRequestInitializers
      }
      class RestTemplate{
          -List<HttpMessageConverter<?>> messageConverters
          -ResponseErrorHandler errorHandler
          -UriTemplateHandler uriTemplateHandler
          -ResponseExtractor<HttpHeaders> headersExtractor
      }
      class RestOperations{
       
      }

从RestTemplate代码可知, 其继承抽象类InterceptingHttpAccessor,而InterceptingHttpAccessor又是继承自抽象类HttpAccessor, 两个抽象父类, 都比较简单. 其中HttpAccessor中主要是定义了客户端请求工厂对象和客户端请求初始化对象(其中RestTemplate想要切换不同的Http客户端请求,就实现不同的客户端请求工厂即可).而InterceptingHttpAccessor中主要定义了客户端请求拦截对象, 而RestTemplate中主要是类型转换对象(其中包含各种常用的如json,gson等), 响应异常处理等. 此外, RestTemplate还实现了RestOperations接口, 该接口定义了很多Http类型的请求操作方法, 主要有以下几类: GET, HEAD , POST, PUT,PATCH, DELETE , OPTIONS , exchange,General execution通用执行.

HTTP方法 RestTemplate方法
GET getForObject getForEntity
HEAD headForHeaders
POST postForLocation postForObject postForEntity
PUT put
PATCH patchForObject
DELETE delete
OPTIONS optionsForAllow
exchange exchange
General execution execute

RestTemplate部分源码

public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
    
    private static final boolean romePresent;

	private static final boolean jaxb2Present;

	private static final boolean jackson2Present;

	private static final boolean jackson2XmlPresent;

	private static final boolean jackson2SmilePresent;

	private static final boolean jackson2CborPresent;

	private static final boolean gsonPresent;

	private static final boolean jsonbPresent;

	static {
		ClassLoader classLoader = RestTemplate.class.getClassLoader();
		romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
		jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
		jackson2Present =
				ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
						ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
		jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
		jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
		jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
		gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
		jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
	}
    
    
    // 构造 通过Http请求工厂创建一个实例对象
	public RestTemplate(ClientHttpRequestFactory requestFactory) {
		this();
		setRequestFactory(requestFactory);
	}
    
    
}    
复制代码

RestTemplate常用配置

@Slf4j
@Configuration
public class RestTemplateConfig {

    /**
     * 连接池最大连接数
     */
    private int maxTotal = 500;

    /**
     * 同路由并发数
     */
    private int defaultMaxPerRoute = 50;

    /**
     * 重试次数
     */
    private int retryCount = 3;

    /**
     * 连接超时时间/毫秒 超出该时间抛出connect timeout
     */
    private int connectTimeout = 5000;

    /**
     * 数据读取超时时间(socketTimeout)/毫秒 超出该时间抛出 read timeout
     */
    private int readTimeout = 5000;

    /**
     * 请求连接的超时时间/毫秒  从连接池中获取连接的超时时间,超过该时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException:
     * Timeout waiting for connection from pool
     */
    private int connectionRequestTimeout = 500;

    private OkHttp3ClientHttpRequestFactory okHttp3ClientHttpRequestFactory;

    /**
     * Http连接管理
     */
    @Bean
    public HttpClientConnectionManager httpClientConnectionManager() {
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(
                30, TimeUnit.SECONDS);

        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory
                        .getSocketFactory()).build();

        // 默认构造就是 注册http和https请求  也可通过上述方式手动添加
        // PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(
                registry);
        poolingHttpClientConnectionManager.setMaxTotal(maxTotal);
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
        return poolingHttpClientConnectionManager;
    }


    /**
     * Http客户端
     */
    @Bean
    public HttpClient httpClient(HttpClientConnectionManager httpClientConnectionManager) {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

        // 请求配置  可以在Http客户端构建的过程中配置, 也可以在请求工厂类中配置
/*        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(readTimeout)
                .setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectionRequestTimeout)
                .build();*/

        // 请求头参数
        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
                // 添加请求配置
                // .setDefaultRequestConfig(requestConfig)
                // 添加Http连接管理
                .setConnectionManager(httpClientConnectionManager)
                // 添加请求头
                .setDefaultHeaders(headers)
                // 保持长连接配置,需要在头添加Keep-Alive
                .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
                // 设置重试次数 默认是3次
                .setRetryHandler(new DefaultHttpRequestRetryHandler(retryCount, true))
                .build();
    }


    /**
     * 客户端请求工厂配置
     */
    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
        // 创建org.apache.http.client请求工厂
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(
                httpClient);
        // 客户端请求配置
        clientHttpRequestFactory.setConnectTimeout(connectTimeout);
        clientHttpRequestFactory.setReadTimeout(readTimeout);
        clientHttpRequestFactory.setConnectionRequestTimeout(connectionRequestTimeout);

        return clientHttpRequestFactory;
    }

    @Bean
    @Primary  // 表示优先使用该注解的Bean
    @LoadBalanced // 让RestTemplate具备负载均衡的能力 (下篇讲)
    public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
        // 初始化RestTemplate, 添加默认的类型转换
        RestTemplate restTemplate = new RestTemplate();

        // 添加请求工厂  此处添加的是org.apache.http.client请求工厂,  如要替换成OkHttpClient请求, 则换成OkHttp3ClientHttpRequestFactory请求工厂
        restTemplate.setRequestFactory(clientHttpRequestFactory);

        // 重新设置 StringHttpMessageConverter 字符集为UTF-8,解决中文乱码问题
        List<HttpMessageConverter<?>> httpMessageConverterList = restTemplate
                .getMessageConverters();
        HttpMessageConverter<?> httpMessageConverter = null;
        for (HttpMessageConverter<?> messageConverter : httpMessageConverterList) {
            if (messageConverter instanceof StringHttpMessageConverter) {
                httpMessageConverter = messageConverter;
                break;
            }
        }
        // 先删除 StringHttpMessageConverter 类型转换
        if (null != httpMessageConverter) {
            httpMessageConverterList.remove(httpMessageConverter);
        }
        // 添加 StringHttpMessageConverter 类型转换   设置字符集为UTF-8 (该处默认的是 ISO-8859-1 )
        httpMessageConverterList.add(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));

        // 加入FastJson转换器
        httpMessageConverterList.add(new FastJsonHttpMessageConverter());

        // 添加请求头拦截器  在请求头添加一些信息如 token等 (可选)
        List<ClientHttpRequestInterceptor> clientHttpRequestInterceptorList = new ArrayList<>();
        clientHttpRequestInterceptorList.add(new MyInterceptor());
        restTemplate.setInterceptors(clientHttpRequestInterceptorList);

        return restTemplate;
    }

}
复制代码

自定义客户端请求拦截器

public class MyInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body,
            ClientHttpRequestExecution execution) throws IOException {
        // 请求头添加信息  如唯一标识  token等
        request.getHeaders().set("token","token值");
        return execution.execute(request,body);
    }
}
复制代码

2 RestTemplate的使用

准备两个SpringBoot微服务, consumer-a和provider-a, 然后服务consumer-a通过RestTemplate调用provider-a,查看调用结果.

consumer-a服务

RestTemplate配置文件

@Slf4j
@Configuration
public class RestTemplateConfig {

    /**
     * 连接池最大连接数
     */
    private int maxTotal = 500;

    /**
     * 同路由并发数
     */
    private int defaultMaxPerRoute = 50;

    /**
     * 重试次数
     */
    private int retryCount = 3;

    /**
     * 连接超时时间/毫秒 超出该时间抛出connect timeout
     */
    private int connectTimeout = 5000;

    /**
     * 数据读取超时时间(socketTimeout)/毫秒 超出该时间抛出 read timeout
     */
    private int readTimeout = 5000;

    /**
     * 请求连接的超时时间/毫秒  从连接池中获取连接的超时时间,超过该时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException:
     * Timeout waiting for connection from pool
     */
    private int connectionRequestTimeout = 500;

    /**
     * Http连接管理
     */
    @Bean
    public HttpClientConnectionManager httpClientConnectionManager() {
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);

        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory
                        .getSocketFactory()).build();

        // 默认构造就是 注册http和https请求  也可通过上述方式手动添加
        // PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(
                registry);
        poolingHttpClientConnectionManager.setMaxTotal(maxTotal);
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
        return poolingHttpClientConnectionManager;
    }


    /**
     * Http客户端
     */
    @Bean
    public HttpClient httpClient(HttpClientConnectionManager httpClientConnectionManager) {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();


        // 请求配置  可以在Http客户端构建的过程中配置, 也可以在请求工厂类中配置
/*        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(readTimeout)
                .setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectionRequestTimeout)
                .build();*/

        // 请求头参数
        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
                // 添加请求配置
                // .setDefaultRequestConfig(requestConfig)
                // 添加Http连接管理
                .setConnectionManager(httpClientConnectionManager)
                // 添加请求头
                .setDefaultHeaders(headers)
                // 保持长连接配置,需要在头添加Keep-Alive
                .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
                // 设置重试次数 默认是3次
                .setRetryHandler(new DefaultHttpRequestRetryHandler(retryCount, true))
                .build();
    }


    /**
     * 客户端请求工厂配置
     */
    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
        // 创建org.apache.http.client请求工厂
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
        // 客户端请求配置
        clientHttpRequestFactory.setConnectTimeout(connectTimeout);
        clientHttpRequestFactory.setReadTimeout(readTimeout);
        clientHttpRequestFactory.setConnectionRequestTimeout(connectionRequestTimeout);

        return clientHttpRequestFactory;
    }
    
    
/*    
    // okhttpclient 构建
    @Bean
    public OkHttpClient okHttpClient(){
        OkHttpClient okHttpClient = new OkHttpClient();
        okHttpClient.newBuilder()
                .connectTimeout(connectTimeout,TimeUnit.MILLISECONDS)
                .readTimeout(readTimeout,TimeUnit.MILLISECONDS)
                .build();

        return okHttpClient;
    }

    *//**
     * 客户端请求工厂配置
     *//*
    @Bean
    public ClientHttpRequestFactory requestFactory(OkHttpClient okHttpClient) {
        System.out.println("使用okhttpclient");
        // 创建OkHttpClient请求工厂
        OkHttp3ClientHttpRequestFactory clientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(okHttpClient);
        // 客户端请求配置
        clientHttpRequestFactory.setConnectTimeout(connectTimeout);
        clientHttpRequestFactory.setReadTimeout(readTimeout);
        clientHttpRequestFactory.setWriteTimeout(connectionRequestTimeout);
        return clientHttpRequestFactory;
    }
*/
    
    @Bean
    @Primary  // 表示优先使用该注解的Bean
    @LoadBalanced // 让RestTemplate具备负载均衡的能力 (下篇讲)
    public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
        // 初始化RestTemplate, 添加默认的类型转换
        RestTemplate restTemplate = new RestTemplate();

        // 添加请求工厂  此处添加的是org.apache.http.client请求工厂,  如要替换成OkHttpClient请求, 则换成OkHttp3ClientHttpRequestFactory请求工厂
        restTemplate.setRequestFactory(clientHttpRequestFactory);

        // 重新设置 StringHttpMessageConverter 字符集为UTF-8,解决中文乱码问题
        List<HttpMessageConverter<?>> httpMessageConverterList = restTemplate
                .getMessageConverters();
        HttpMessageConverter<?> httpMessageConverter = null;
        for (HttpMessageConverter<?> messageConverter : httpMessageConverterList) {
            if (messageConverter instanceof StringHttpMessageConverter) {
                httpMessageConverter = messageConverter;
                break;
            }
        }
        // 先删除 StringHttpMessageConverter 类型转换
        if (null != httpMessageConverter) {
            httpMessageConverterList.remove(httpMessageConverter);
        }
        // 添加 StringHttpMessageConverter 类型转换   设置字符集为UTF-8 (该处默认的是 ISO-8859-1 )
        httpMessageConverterList.add(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));

        // 加入FastJson转换器
        httpMessageConverterList.add(new FastJsonHttpMessageConverter());

        // 添加请求头拦截器  在请求头添加一些信息如 token等 (可选,不次不用,故不添加)
        // List<ClientHttpRequestInterceptor> clientHttpRequestInterceptorList = new ArrayList<>();
        // clientHttpRequestInterceptorList.add(new MyInterceptor());
        // restTemplate.setInterceptors(clientHttpRequestInterceptorList);
        return restTemplate;
    }

}
复制代码

Controller控制器

@RestController
@Slf4j
@Api(value = "AConController", tags = "消费者控制器")
public class AConController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/consumer")
    public String list() {
        String info = "我是consumerA,8081    ";
        log.info(info);

        // 在RestTemplate使用@LoadBalanced注解后, 可简化为且必须要使用服务名,如provider-a (使用ip和端口 访问不到)
        // 没有添加该注解时, 需要使用ip和端口, 如 127.0.0.1:9091 (使用服务名, 访问不到)
        // String url = "http://127.0.0.1:9091/provider";
        String url = "http://provider-a/provider"; 
        String result = restTemplate.getForObject(url, String.class);

        return JSON.toJSONString(info + result);
    }

}
复制代码

application.yml配置文件

server:
  port: 8081
spring:
  cloud:
    nacos:
      discovery:
        server-addr:  127.0.0.1:8848 # 配置nacos 服务端地址
  application:
    name: consumer-a # 服务名称
复制代码

provider-a服务

Controller控制器

@RestController
@Slf4j
@Api(value = "AProController", tags = "提供者控制器")
public class AProController {

    @GetMapping("/provider")
    public String list() {
        String info = "我是 providerA,9091  ";
        log.info(info);
        return JSON.toJSONString(info);
    }
}
复制代码

application.yml配置文件

server:
  port: 9091
spring:
  cloud:
    nacos:
      discovery:
        server-addr:  127.0.0.1:8848 # 配置nacos 服务端地址
  application:
    name: provider-a # 服务名称
复制代码

测试功能

1 启动本地nacos服务

2 依次启动provider-a服务, consumer-a服务

3 访问 http://localhost:8081/consumer 响应结果

"我是consumerA,8081    \"我是 providerA,9091  \""
复制代码

4 构建RestTemplate时去掉@LoadBalanced , RestTemplate使用时, 使用ip和端口, 响应结果

"我是consumerA,8081    \"我是 providerA,9091  \""
复制代码

通过以上测试,发现RestTemplate在各个服务之间的调用也非常方便, 而且可以根据不同的业务场景,来切换不同的客户端请求对象.

使用过程中遇到的问题

java.lang.IllegalStateException: No instances available for localhost

最初RestTemplate调用过程中,出现了java.lang.IllegalStateException: No instances available for localhost报错异常, 查询资料后得知, 使用了@LoadBalanced, 就不能使用ip和端口的样式去调用服务,而是使用服务名称来调用.

猜你喜欢

转载自juejin.im/post/7036338666951671815