如果项目里可能只是偶尔通过一个url,发起一个http请求,一个基本实现如下:
@Configuration
public class RestTemplateConfiguration {@Beanpublic RestTemplate restTemplate() {RestTemplate restTemplate = new RestTemplate();// 这个转换器非必须,根据自己的场景添加restTemplate.getMessageConverters().set(1,new StringHttpMessageConverter(Charset.forName("UTF-8")));return restTemplate;}
}
进行一下测试:
很好,没有问题。
默认实现,比如读取超时或者连接超时是没有限制,我们想自定义,实现如下:
@Beanpublic RestTemplate restTemplate(ClientHttpRequestFactory factory) {RestTemplate restTemplate = new RestTemplate(factory);// 这个转换器非必须,根据自己的场景添加restTemplate.getMessageConverters().set(1,new StringHttpMessageConverter(Charset.forName("UTF-8")));return restTemplate;}@Beanpublic 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 = newHttpComponentsClientHttpRequestFactory(client);clientHttpRequestFactory.setConnectTimeout(httpClientProperties.getConnectTimeout());clientHttpRequestFactory.setReadTimeout(httpClientProperties.getReadTimeout());clientHttpRequestFactory.setConnectionRequestTimeout(httpClientProperties.getAcquireConnectionTimeout());return clientHttpRequestFactory;}@Beanpublic 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 socketFactoryRegistry = RegistryBuilder.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 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 getDefaultHeaders() {List headers = new ArrayList<>();headers.add(new BasicHeader("Connection", "Keep-Alive"));return headers;}class InsecureTrustManager implements X509TrustManager {@Overridepublic void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}@Overridepublic void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}/*** 返回受信任的X509证书数组*/@Overridepublic 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")@Beanpublic RestTemplate restTemplate(ClientHttpRequestFactory factory) {RestTemplate restTemplate = new RestTemplate(factory);restTemplate.getMessageConverters().set(1,new StringHttpMessageConverter(Charset.forName("UTF-8")));return restTemplate;}
我们的项目里集成了注册中心,需要支持服务发现,负载均衡调用,加个注解即可,如下:
@LoadBalanced@Beanpublic RestTemplate restTemplate() {RestTemplate restTemplate = new RestTemplate();return restTemplate;}
注意,这种的就不支持普通的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的时候不希望抛异常,而由我们自己获取判断处理,可以如下定义:
@Beanpublic RestTemplate restTemplate() {RestTemplate restTemplate = new RestTemplate();restTemplate.getMessageConverters().set(1,new StringHttpMessageConverter(Charset.forName("UTF-8")));restTemplate.setErrorHandler(new ResponseErrorHandler() {@Overridepublic boolean hasError(ClientHttpResponse response) throws IOException {return false;}@Overridepublic void handleError(ClientHttpResponse response) throws IOException {}});return restTemplate;}