适配不同场景的RestTemplate

Posted 不识君的荒漠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了适配不同场景的RestTemplate相关的知识,希望对你有一定的参考价值。

一个基本实现

如果项目里可能只是偶尔通过一个url,发起一个http请求,一个基本实现如下:

@Configuration
public class RestTemplateConfiguration 

    @Bean
    public RestTemplate restTemplate() 
        RestTemplate restTemplate = new RestTemplate();
        // 这个转换器非必须,根据自己的场景添加
        restTemplate.getMessageConverters().set(1,
                new StringHttpMessageConverter(Charset.forName("UTF-8")));
        return restTemplate;
    

进行一下测试:

很好,没有问题。

配置超时

默认实现,比如读取超时或者连接超时是没有限制,我们想自定义,实现如下:

    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) 
        RestTemplate restTemplate = new RestTemplate(factory);
        // 这个转换器非必须,根据自己的场景添加
        restTemplate.getMessageConverters().set(1,
                new StringHttpMessageConverter(Charset.forName("UTF-8")));
        return restTemplate;
    

    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory() 
        SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
        simpleClientHttpRequestFactory.setReadTimeout(3000);
        simpleClientHttpRequestFactory.setReadTimeout(3000);
        return simpleClientHttpRequestFactory;
    

我们可以定义一个ClientHttpRequestFactory并配置超时时间来初始化RestTemplate。

RestTemplate默认也是用的ClientHttpRequestFactory的。

支持连接池

默认的ClientHttpRequestFactory使用的是HttpUrlConnection,本身是不支持连接池的,这个时候我们需要启用连接池的实现来提高一个吞吐量或者减少请求响应时间,怎么办。

替换默认的ClientHttpRequestFactory,如下,我们使用支持 Apache HttpComponents HttpClient的实现。

    @Bean(name = "clientHttpRequestFactory")
    public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient client) 

        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new
                HttpComponentsClientHttpRequestFactory(client);
        clientHttpRequestFactory.setConnectTimeout(httpClientProperties.getConnectTimeout());
        clientHttpRequestFactory.setReadTimeout(httpClientProperties.getReadTimeout());
        clientHttpRequestFactory.setConnectionRequestTimeout(httpClientProperties.getAcquireConnectionTimeout());
        return clientHttpRequestFactory;
    

    @Bean
    public HttpClient httpClient() 
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        try 
            // 针对https协议相关配置
            SSLContext sslContext = SSLContext.getInstance("SSL");// 获取一个SSLContext实例
            TrustManager[] trustAllCerts = new InsecureTrustManager();
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());// 初始化SSLContext实例

            //设置信任ssl访问
            httpClientBuilder.setSSLContext(sslContext);
            HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    // 注册http和https请求
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", sslConnectionSocketFactory).build();
            PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            poolingHttpClientConnectionManager.setMaxTotal(httpClientProperties.getMaxConnection());
            poolingHttpClientConnectionManager.setDefaultMaxPerRoute(httpClientProperties.getMaxConnectionRoute());
            httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
            httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(httpClientProperties.getRetryTimes(), true));

            //设置默认请求头
            List<Header> headers = getDefaultHeaders();
            httpClientBuilder.setDefaultHeaders(headers);

            httpClientBuilder.evictExpiredConnections();
            httpClientBuilder.evictIdleConnections(httpClientProperties.getIdleTime(), TimeUnit.MINUTES);
            CloseableHttpClient httpClient = httpClientBuilder.build();
            Runtime.getRuntime().addShutdownHook(new Thread(() -> 
                try 
                    httpClient.close();
                 catch (IOException e) 
                    log.error("close http client error.", e);
                
            ));
            return httpClient;
         catch (Exception e) 
            log.error("HttpClient create error.", e);
        
        return null;
    

    private List<Header> getDefaultHeaders() 
        List<Header> headers = new ArrayList<>();
        headers.add(new BasicHeader("Connection", "Keep-Alive"));
        return headers;
    

    class InsecureTrustManager implements X509TrustManager 

        @Override
        public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException 

        @Override
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException 

        /**
         * 返回受信任的X509证书数组
         */
        @Override
        public X509Certificate[] getAcceptedIssuers() 
            return null;
        

    

 好吧,这里的代码就有点多了,其中使用的HttpClientProperties类主要是其中的一些属性配置,如下:

public class HttpClientProperties 

    private int readTimeout;

    private int connectTimeout;

    private int acquireConnectionTimeout;

    private int maxConnection;

    private int maxConnectionRoute;

    private int retryTimes;

    private int idleTime;

这样,再创建RestTemplate的时候,我们可以指定使用支持连接池的ClientHttpRequestFactory,如下:

    @Resource(name = "clientHttpRequestFactory")
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) 
        RestTemplate restTemplate = new RestTemplate(factory);
        restTemplate.getMessageConverters().set(1,
                new StringHttpMessageConverter(Charset.forName("UTF-8")));
        return restTemplate;
    

支持服务发现

我们的项目里集成了注册中心,需要支持服务发现,负载均衡调用,加个注解即可,如下:

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() 
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate;
    

注意,这种的就不支持普通的url调用了。

服务发现与普通URL

如果项目里既需要支持服务发现进行调用,又需要调用一些常规的http url的接口,也很简单,定义两个,需要哪个注入哪个:

    @LoadBalanced
    @Bean(name = "loadBalancedRestTemplate")
    public RestTemplate loadBalancedRestTemplate() 
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate;
    

    @Bean(name = "restTemplate")
    public RestTemplate restTemplate() 
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate;
    

响应状态错误码处理

默认实现,如果是4xx或5xx错误,请求响应的时候会抛出异常:

 在我们的一些场景中,某些接口可能会通过返回不同的状态码来返回不同的错误信息,而不是都返回一个200的状态码,在消息体使用code等字段来表示,如果是4xx或5xx的时候不希望抛异常,而由我们自己获取判断处理,可以如下定义:

    @Bean
    public RestTemplate restTemplate() 
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getMessageConverters().set(1,
                new StringHttpMessageConverter(Charset.forName("UTF-8")));
        restTemplate.setErrorHandler(new ResponseErrorHandler() 
            @Override
            public boolean hasError(ClientHttpResponse response) throws IOException 
                return false;
            

            @Override
            public void handleError(ClientHttpResponse response) throws IOException 

            
        );
        return restTemplate;
    

以上是关于适配不同场景的RestTemplate的主要内容,如果未能解决你的问题,请参考以下文章

Sentinel对RestTemplate和Feign的适配

flutter屏幕适配

iOS14隐私适配:根据不同的场景需求设置不同的定位精确度

iOS14隐私适配:根据不同的场景需求设置不同的定位精确度

用CSS做iOS和Android样式适配

屏幕适配