From a3cc973e4201ffe63001fa4de2138667d02f6d35 Mon Sep 17 00:00:00 2001 From: Xiong Neng Date: Sat, 15 Sep 2018 15:29:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90springboot-resttemplate?= =?UTF-8?q?=E5=8D=87=E7=BA=A7=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- springboot-resttemplate/pom.xml | 11 ++ .../xncoding/pos/config/HttpClientConfig.java | 153 ++++++++++++++++++ .../xncoding/pos/config/RestClientConfig.java | 85 ---------- .../pos/config/RestTemplateConfig.java | 52 ++++++ .../properties/HttpClientProperties.java | 99 ++++++++++++ .../src/main/resources/application.yml | 25 ++- .../com/xncoding/pos/ApplicationTests.java | 2 - 7 files changed, 324 insertions(+), 103 deletions(-) create mode 100644 springboot-resttemplate/src/main/java/com/xncoding/pos/config/HttpClientConfig.java delete mode 100644 springboot-resttemplate/src/main/java/com/xncoding/pos/config/RestClientConfig.java create mode 100644 springboot-resttemplate/src/main/java/com/xncoding/pos/config/RestTemplateConfig.java create mode 100644 springboot-resttemplate/src/main/java/com/xncoding/pos/config/properties/HttpClientProperties.java diff --git a/springboot-resttemplate/pom.xml b/springboot-resttemplate/pom.xml index 0288a95..c7d798e 100644 --- a/springboot-resttemplate/pom.xml +++ b/springboot-resttemplate/pom.xml @@ -25,6 +25,17 @@ + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-aop + org.springframework.boot spring-boot-starter-web diff --git a/springboot-resttemplate/src/main/java/com/xncoding/pos/config/HttpClientConfig.java b/springboot-resttemplate/src/main/java/com/xncoding/pos/config/HttpClientConfig.java new file mode 100644 index 0000000..e4a7320 --- /dev/null +++ b/springboot-resttemplate/src/main/java/com/xncoding/pos/config/HttpClientConfig.java @@ -0,0 +1,153 @@ +package com.xncoding.pos.config; + +import com.xncoding.pos.config.properties.HttpClientProperties; +import org.apache.http.HeaderElement; +import org.apache.http.HeaderElementIterator; +import org.apache.http.HttpResponse; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.message.BasicHeaderElementIterator; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.http.ssl.SSLContextBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +import javax.annotation.Resource; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; +import java.util.concurrent.TimeUnit; + +/** + * - Supports both HTTP and HTTPS + * - Uses a connection pool to re-use connections and save overhead of creating connections. + * - Has a custom connection keep-alive strategy (to apply a default keep-alive if one isn't specified) + * - Starts an idle connection monitor to continuously clean up stale connections. + * + * @author XiongNeng + * @version 1.0 + * @since 2018/7/5 + */ +@Configuration +@EnableScheduling +public class HttpClientConfig { + + private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientConfig.class); + + @Resource + private HttpClientProperties p; + + @Bean + public PoolingHttpClientConnectionManager poolingConnectionManager() { + SSLContextBuilder builder = new SSLContextBuilder(); + try { + builder.loadTrustMaterial(null, new TrustStrategy() { + public boolean isTrusted(X509Certificate[] arg0, String arg1) { + return true; + } + }); + } catch (NoSuchAlgorithmException | KeyStoreException e) { + LOGGER.error("Pooling Connection Manager Initialisation failure because of " + e.getMessage(), e); + } + + SSLConnectionSocketFactory sslsf = null; + try { + sslsf = new SSLConnectionSocketFactory(builder.build()); + } catch (KeyManagementException | NoSuchAlgorithmException e) { + LOGGER.error("Pooling Connection Manager Initialisation failure because of " + e.getMessage(), e); + } + + Registry socketFactoryRegistry = RegistryBuilder + .create() + .register("https", sslsf) + .register("http", new PlainConnectionSocketFactory()) + .build(); + + PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); + poolingConnectionManager.setMaxTotal(p.getMaxTotalConnections()); //最大连接数 + poolingConnectionManager.setDefaultMaxPerRoute(p.getDefaultMaxPerRoute()); //同路由并发数 + return poolingConnectionManager; + } + + @Bean + public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() { + return new ConnectionKeepAliveStrategy() { + @Override + public long getKeepAliveDuration(HttpResponse response, HttpContext httpContext) { + HeaderElementIterator it = new BasicHeaderElementIterator + (response.headerIterator(HTTP.CONN_KEEP_ALIVE)); + while (it.hasNext()) { + HeaderElement he = it.nextElement(); + String param = he.getName(); + String value = he.getValue(); + if (value != null && param.equalsIgnoreCase("timeout")) { + return Long.parseLong(value) * 1000; + } + } + return p.getDefaultKeepAliveTimeMillis(); + } + }; + } + + @Bean + public CloseableHttpClient httpClient() { + RequestConfig requestConfig = RequestConfig.custom() + .setConnectionRequestTimeout(p.getRequestTimeout()) + .setConnectTimeout(p.getConnectTimeout()) + .setSocketTimeout(p.getSocketTimeout()).build(); + + return HttpClients.custom() + .setDefaultRequestConfig(requestConfig) + .setConnectionManager(poolingConnectionManager()) + .setKeepAliveStrategy(connectionKeepAliveStrategy()) + .setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)) // 重试次数 + .build(); + } + + @Bean + public Runnable idleConnectionMonitor(final PoolingHttpClientConnectionManager connectionManager) { + return new Runnable() { + @Override + @Scheduled(fixedDelay = 10000) + public void run() { + try { + if (connectionManager != null) { + LOGGER.trace("run IdleConnectionMonitor - Closing expired and idle connections..."); + connectionManager.closeExpiredConnections(); + connectionManager.closeIdleConnections(p.getCloseIdleConnectionWaitTimeSecs(), TimeUnit.SECONDS); + } else { + LOGGER.trace("run IdleConnectionMonitor - Http Client Connection manager is not initialised"); + } + } catch (Exception e) { + LOGGER.error("run IdleConnectionMonitor - Exception occurred. msg={}, e={}", e.getMessage(), e); + } + } + }; + } + + @Bean + public TaskScheduler taskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setThreadNamePrefix("poolScheduler"); + scheduler.setPoolSize(50); + return scheduler; + } +} diff --git a/springboot-resttemplate/src/main/java/com/xncoding/pos/config/RestClientConfig.java b/springboot-resttemplate/src/main/java/com/xncoding/pos/config/RestClientConfig.java deleted file mode 100644 index 22fa007..0000000 --- a/springboot-resttemplate/src/main/java/com/xncoding/pos/config/RestClientConfig.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.xncoding.pos.config; - -import org.apache.http.client.HttpClient; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.socket.PlainConnectionSocketFactory; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.conn.ssl.TrustStrategy; -import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.apache.http.ssl.SSLContextBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.web.client.DefaultResponseErrorHandler; -import org.springframework.web.client.RestTemplate; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLContext; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -/** - * RestTemplate客户端连接池配置 - * - * @author XiongNeng - * @version 1.0 - * @since 2018/1/24 - */ -@Configuration -public class RestClientConfig { - - private Logger log = LoggerFactory.getLogger(this.getClass()); - @Bean - public RestTemplate restTemplate() { - RestTemplate restTemplate = new RestTemplate(); - restTemplate.setRequestFactory(clientHttpRequestFactory()); - restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); - return restTemplate; - } - - @Bean - public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() { - try { - HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); - SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() { - public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { - return true; - } - }).build(); - httpClientBuilder.setSSLContext(sslContext); - HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE; - SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier); - Registry socketFactoryRegistry = RegistryBuilder.create() - .register("http", PlainConnectionSocketFactory.getSocketFactory()) - .register("https", sslConnectionSocketFactory).build();// 注册http和https请求 - // 开始设置连接池 - PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); - poolingHttpClientConnectionManager.setMaxTotal(500); // 最大连接数500 - poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100); // 同路由并发数100 - httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager); - httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)); // 重试次数 - HttpClient httpClient = httpClientBuilder.build(); - HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); // httpClient连接配置 - clientHttpRequestFactory.setConnectTimeout(20000); // 连接超时 - clientHttpRequestFactory.setReadTimeout(30000); // 数据读取超时时间 - clientHttpRequestFactory.setConnectionRequestTimeout(20000); // 连接不够用的等待时间 - return clientHttpRequestFactory; - } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) { - log.error("初始化HTTP连接池出错", e); - } - return null; - } -} diff --git a/springboot-resttemplate/src/main/java/com/xncoding/pos/config/RestTemplateConfig.java b/springboot-resttemplate/src/main/java/com/xncoding/pos/config/RestTemplateConfig.java new file mode 100644 index 0000000..e07b3d1 --- /dev/null +++ b/springboot-resttemplate/src/main/java/com/xncoding/pos/config/RestTemplateConfig.java @@ -0,0 +1,52 @@ +package com.xncoding.pos.config; + +import org.apache.http.impl.client.CloseableHttpClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import javax.annotation.Resource; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +/** + * RestTemplate客户端连接池配置 + * + * @author XiongNeng + * @version 1.0 + * @since 2018/1/24 + */ +@Configuration +@EnableAspectJAutoProxy(proxyTargetClass = true) +public class RestTemplateConfig { + + @Resource + private CloseableHttpClient httpClient; + + @Bean + public RestTemplate restTemplate(MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) { + RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory()); + + List> messageConverters = new ArrayList<>(); + StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(Charset.forName("utf-8")); + messageConverters.add(stringHttpMessageConverter); + messageConverters.add(jackson2HttpMessageConverter); + restTemplate.setMessageConverters(messageConverters); + + return restTemplate; + } + + @Bean + public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() { + HttpComponentsClientHttpRequestFactory rf = new HttpComponentsClientHttpRequestFactory(); + rf.setHttpClient(httpClient); + return rf; + } + +} diff --git a/springboot-resttemplate/src/main/java/com/xncoding/pos/config/properties/HttpClientProperties.java b/springboot-resttemplate/src/main/java/com/xncoding/pos/config/properties/HttpClientProperties.java new file mode 100644 index 0000000..bc99b99 --- /dev/null +++ b/springboot-resttemplate/src/main/java/com/xncoding/pos/config/properties/HttpClientProperties.java @@ -0,0 +1,99 @@ +package com.xncoding.pos.config.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * HttpClient连接池配置 + * + * @author xiongneng + * @since 2018/7/09 22:31 + */ +@Component +@ConfigurationProperties(prefix = "httpclient") +public class HttpClientProperties { + /** + * 建立连接的超时时间 + */ + private int connectTimeout = 20000; + /** + * 连接不够用的等待时间 + */ + private int requestTimeout = 20000; + /** + * 每次请求等待返回的超时时间 + */ + private int socketTimeout = 30000; + /** + * 每个主机最大连接数 + */ + private int defaultMaxPerRoute = 100; + /** + * 最大连接数 + */ + private int maxTotalConnections = 300; + /** + * 默认连接保持活跃的时间 + */ + private int defaultKeepAliveTimeMillis = 20000; + /** + * 空闲连接生的存时间 + */ + private int closeIdleConnectionWaitTimeSecs = 30; + + public int getConnectTimeout() { + return connectTimeout; + } + + public void setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + } + + public int getRequestTimeout() { + return requestTimeout; + } + + public void setRequestTimeout(int requestTimeout) { + this.requestTimeout = requestTimeout; + } + + public int getSocketTimeout() { + return socketTimeout; + } + + public void setSocketTimeout(int socketTimeout) { + this.socketTimeout = socketTimeout; + } + + public int getDefaultMaxPerRoute() { + return defaultMaxPerRoute; + } + + public void setDefaultMaxPerRoute(int defaultMaxPerRoute) { + this.defaultMaxPerRoute = defaultMaxPerRoute; + } + + public int getMaxTotalConnections() { + return maxTotalConnections; + } + + public void setMaxTotalConnections(int maxTotalConnections) { + this.maxTotalConnections = maxTotalConnections; + } + + public int getDefaultKeepAliveTimeMillis() { + return defaultKeepAliveTimeMillis; + } + + public void setDefaultKeepAliveTimeMillis(int defaultKeepAliveTimeMillis) { + this.defaultKeepAliveTimeMillis = defaultKeepAliveTimeMillis; + } + + public int getCloseIdleConnectionWaitTimeSecs() { + return closeIdleConnectionWaitTimeSecs; + } + + public void setCloseIdleConnectionWaitTimeSecs(int closeIdleConnectionWaitTimeSecs) { + this.closeIdleConnectionWaitTimeSecs = closeIdleConnectionWaitTimeSecs; + } +} diff --git a/springboot-resttemplate/src/main/resources/application.yml b/springboot-resttemplate/src/main/resources/application.yml index ea74468..ce23aa9 100644 --- a/springboot-resttemplate/src/main/resources/application.yml +++ b/springboot-resttemplate/src/main/resources/application.yml @@ -22,25 +22,18 @@ logging: spring: profiles: dev -logging: - level: - ROOT: INFO - com: - xncoding: DEBUG - file: E:/logs/app.log - ---- - -##################################################################### -######################## 测试环境profile ########################## -##################################################################### - -spring: - profiles: test +httpclient: + connectTimeout: 20000 + requestTimeout: 20000 + socketTimeout: 30000 + defaultMaxPerRoute: 100 + maxTotalConnections: 300 + defaultKeepAliveTimeMillis: 20000 + closeIdleConnectionWaitTimeSecs: 30 logging: level: ROOT: INFO com: xncoding: DEBUG - file: /var/logs/app.log + file: D:/logs/app.log diff --git a/springboot-resttemplate/src/test/java/com/xncoding/pos/ApplicationTests.java b/springboot-resttemplate/src/test/java/com/xncoding/pos/ApplicationTests.java index d165dd2..4941e98 100644 --- a/springboot-resttemplate/src/test/java/com/xncoding/pos/ApplicationTests.java +++ b/springboot-resttemplate/src/test/java/com/xncoding/pos/ApplicationTests.java @@ -39,7 +39,6 @@ public class ApplicationTests { @Test public void testRestTemplate() { - logger.info("解绑成功后推送消息给对应的POS机"); LoginParam param = new LoginParam(); param.setUsername("admin"); param.setPassword("12345678"); @@ -47,7 +46,6 @@ public class ApplicationTests { BaseResponse r = restTemplate.postForObject(loginUrl, param, BaseResponse.class); assertThat(r.isSuccess(), is(true)); - logger.info("推送消息登录认证成功"); String token = (String) r.getData(); UnbindParam unbindParam = new UnbindParam(); unbindParam.setImei("imei");