添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.zzc</groupId>
<artifactId>Demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.zzc.component</groupId>
<artifactId>component</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 公共类、日志依赖等 -->
<!-- <dependency>
<groupId>com.zzc.common</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency> -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- springboot中包含了httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
定义配置类Properties
package com.zzc.component.http;
import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Map;
@Data
@ToString
@ConfigurationProperties(prefix = "httpclient")
public class HttpClientProperties {
private String charset = "UTF-8";
/**
* 总链接数
*/
private Integer connMaxTotal = 3000;
/**
* 并发数量
*/
private Integer maxPerRoute = 1200;
/**
* 重试次数
*/
private Integer retryNum = 1;
/**
* 链接超时
*/
private Integer connectTimeout = 30000;
/**
* 读写超时
*/
private Integer readTimeout = 15000;
/**
* 链接不够用的等待时间,不宜过长,必须设置
*/
private Integer requestTimeout = 200;
/**
* 默认链接保持时间,单位 秒
*/
private Integer keepAliveTime = 60;
/**
* 如果请求目标地址单独配置可长链接保持时间,使用该配置
*/
private Map<String, Integer> keepAliveTargetHost;
/**
* http请求header
*/
private Map<String, String> headers;
}
初始化httpclient
package com.zzc.component.http;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpHost;
import org.apache.http.client.HttpClient;
import org.apache.http.client.protocol.HttpClientContext;
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.impl.client.CloseableHttpClient;
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.message.BasicHeader;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Slf4j
@Configuration
@ConditionalOnClass(value = {RestTemplate.class, CloseableHttpClient.class})
@Import(HttpClientProperties.class)
public class HttpClientFactory {
private HttpClientProperties httpClientProperties;
public HttpClientFactory(HttpClientProperties httpClientProperties) {
this.httpClientProperties = httpClientProperties;
}
@Bean(name = "clientHttpRequestFactory")
public ClientHttpRequestFactory clientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory factory = httpComponentsClientHttpRequestFactory();
return factory;
}
protected HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(httpClientProperties.getConnectTimeout());
factory.setReadTimeout(httpClientProperties.getReadTimeout());
factory.setConnectionRequestTimeout(httpClientProperties.getRequestTimeout());
factory.setHttpClient(httpClient());
return factory;
}
public HttpClient httpClient() {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
try {
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build();
PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager(registry);
httpClientConnectionManager.setMaxTotal(httpClientProperties.getConnMaxTotal());
httpClientConnectionManager.setDefaultMaxPerRoute(httpClientProperties.getMaxPerRoute());
httpClientBuilder.setConnectionManager(httpClientConnectionManager);
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(httpClientProperties.getRetryNum(), httpClientProperties.getRetryNum() != 0));
List<Header> headers = genHeaders();
if (headers != null && !headers.isEmpty()) {
httpClientBuilder.setDefaultHeaders(headers);
}
httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy());
//设置定时关闭无效链接
httpClientBuilder.evictIdleConnections(30L, TimeUnit.SECONDS);
return httpClientBuilder.build();
} catch (Exception e) {
log.error("init http factory error", e);
}
return null;
}
private ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
return ((httpResponse, httpContext) -> {
HeaderElementIterator it = new BasicHeaderElementIterator(httpResponse.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String name = he.getName();
String value = he.getValue();
if (value != null && "timeout".equalsIgnoreCase(name)) {
try {
return Long.parseLong(value) * 1000L;
} catch (NumberFormatException ignore) {
log.error("resolve Keep-Alive timeout", ignore);
}
}
}
HttpHost target = (HttpHost) httpContext.getAttribute(HttpClientContext.HTTP_TARGET_HOST);
//如果请求的目标地址单独做了配置,使用以下的设置
Optional<Map.Entry<String, Integer>> any = Optional.ofNullable(httpClientProperties.getKeepAliveTargetHost())
.orElseGet(HashMap::new)
.entrySet()
.stream()
.filter(e -> e.getKey().equalsIgnoreCase(target.getHostName()))
.findAny();
int keepAliveTime = httpClientProperties.getKeepAliveTime() == null ? 60 : httpClientProperties.getKeepAliveTime();
return any.map(e -> e.getValue() * 1000L).orElse(keepAliveTime * 1000L);
});
}
private List<Header> genHeaders() {
List<Header> headers = new ArrayList<>();
if (httpClientProperties.getHeaders() == null) {
log.warn("init header is null");
return headers;
}
for (Map.Entry<String, String> entry : httpClientProperties.getHeaders().entrySet()) {
headers.add(new BasicHeader(entry.getKey(), entry.getValue()));
}
return headers;
}
private void modifyDefaultCharset(RestTemplate restTemplate) {
List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
HttpMessageConverter<?> converterTarget = null;
for (HttpMessageConverter<?> item : converterList) {
if (StringHttpMessageConverter.class == item.getClass()) {
log.info("HttpMessageConvert exist null");
converterTarget = item;
break;
}
}
if (null != converterTarget) {
converterList.remove(converterTarget);
}
Charset defaultCharset = Charset.forName(httpClientProperties.getCharset());
converterList.add(1, new StringHttpMessageConverter(defaultCharset));
}
private RestTemplate createRestTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
modifyDefaultCharset(restTemplate);
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
return restTemplate;
}
@Bean(name = "httpClientRestTemplate")
public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
return createRestTemplate(clientHttpRequestFactory);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
定义接口
package com.zzc.component.http;
import org.springframework.web.client.ResponseExtractor;
import java.util.Map;
public interface RestTemplateComponent {
<T> T post(String url, Map<String, String> header, Object body, Class<T> clazz);
<T> T post(String url, Object body, Class<T> clazz);
<T> T get(String url, Map<String, String> header, Object params, Class<T> clazz);
<T> T get(String url, Object params, Class<T> clazz);
/**
* get请求的文件代理传输等
* @param url
* @param headerMap
* @param extractor
* @return
* @param <T>
*/
<T> T transfer(String url, Map<String, String> headerMap, ResponseExtractor<T> extractor);
}
实现接口
package com.zzc.component.http;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Slf4j
@Component
public class RestTemplateComponentImpl implements RestTemplateComponent {
@Qualifier("httpClientRestTemplate")
@Autowired
private RestTemplate restTemplate;
@Override
public <T> T post(String url, Map<String, String> header, Object body, Class<T> clazz) {
log.info("rest post url:{}", url);
Set<Map.Entry<String, String>> entries = header.entrySet();
MultiValueMap<String, String> headers = new HttpHeaders();
for (Map.Entry<String, String> entry : entries) {
List<String> objList = new ArrayList<>();
objList.add(entry.getValue());
headers.put(entry.getKey(), objList);
}
HttpEntity<Map<String, Object>> formEntity = new HttpEntity(body, headers);
ResponseEntity<T> response = restTemplate.postForEntity(url, formEntity, clazz);
if (response == null) {
log.error("request error, url:{}", url);
return null;
}
return response.getBody();
}
@Override
public <T> T post(String url, Object body, Class<T> clazz) {
HttpEntity<Map<String, Object>> formEntity = new HttpEntity(body, defaultHeaders());
ResponseEntity<T> response = restTemplate.postForEntity(url, formEntity, clazz);
if (response == null) {
log.error("request error, url:{}", url);
return null;
}
return response.getBody();
}
@Override
public <T> T get(String url, Map<String, String> headers, Object params, Class<T> clazz) {
ResponseEntity<T> response = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(null, generateHeader(headers)), clazz, params);
if (response == null) {
log.error("request error, url:{}", url);
return null;
}
return response.getBody();
}
@Override
public <T> T get(String url, Object params, Class<T> clazz) {
ResponseEntity<T> response = restTemplate.getForEntity(url, clazz, params);
if (response == null) {
log.error("request error, url:{}", url);
return null;
}
return response.getBody();
}
@Override
public <T> T transfer(String url, Map<String, String> headerMap, ResponseExtractor<T> extractor) {
RequestCallback requestCallback = null;
if (headerMap == null || headerMap.isEmpty()) {
requestCallback = restTemplate.httpEntityCallback(HttpEntity.EMPTY);
} else {
HttpEntity<Map<String, Object>> formEntity = new HttpEntity<>(null, generateHeader(headerMap));
requestCallback = restTemplate.httpEntityCallback(formEntity);
}
return restTemplate.execute(url, HttpMethod.GET, requestCallback, extractor);
}
/**
* 如果请求的header,Content-Type=application/x-www-form-urlencoded
* 则需参数接收的载体需要使用 MultiValueMap
* @return
*/
private static MultiValueMap<String, String> defaultHeaders() {
Map<String, String> header = new HashMap<>();
header.put("Content-Type", "application/json;charset=utf-8");
Set<Map.Entry<String, String>> entries = header.entrySet();
MultiValueMap<String, String> headers = new HttpHeaders();
for (Map.Entry<String, String> entry : entries) {
List<String> objList = new ArrayList<>();
objList.add(entry.getValue());
headers.put(entry.getKey(), objList);
}
return headers;
}
private static MultiValueMap<String, String> generateHeader(Map<String, String> headerMap) {
if (headerMap == null || headerMap.isEmpty()) {
return defaultHeaders();
}
MultiValueMap<String, String> headers = new HttpHeaders();
for (Map.Entry<String, String> entry : headerMap.entrySet()) {
List<String> objList = new ArrayList<>();
objList.add(entry.getValue());
headers.put(entry.getKey(), objList);
}
return headers;
}
}