关于Spring的RestTemplate的使用

本文深入介绍了Spring的RestTemplate工具类,包括它的简介、使用场景、核心功能以及如何配置和使用。通过示例展示了在微服务间如何通过RestTemplate进行HTTP请求,同时分享了配置HTTP连接池、超时时间、重试策略等最佳实践。在实际应用中,还可能遇到如服务调用异常等问题,需要正确理解和配置RestTemplate以确保服务间的稳定通信。
摘要由CSDN通过智能技术生成

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

1 RestTemplate的简介

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

HttpAccessor -ClientHttpRequestFactory requestFactory -List<ClientHttpRequestInitializer> clientHttpRequestInitializers InterceptingHttpAccessor -ClientHttpRequestFactory requestFactory -List<ClientHttpRequestInitializer> clientHttpRequestInitializers RestTemplate -List<HttpMessageConverter<?>> messageConverters -ResponseErrorHandler errorHandler -UriTemplateHandler uriTemplateHandler -ResponseExtractor<HttpHeaders> headersExtractor 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方法
GETgetForObject getForEntity
HEADheadForHeaders
POSTpostForLocation postForObject postForEntity
PUTput
PATCHpatchForObject
DELETEdelete
OPTIONSoptionsForAllow
exchangeexchange
General executionexecute

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和端口的样式去调用服务,而是使用服务名称来调用.

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值