上一篇文章 在spring-cloud里面如何使用feign-CSDN博客,我们聊了如何通过openfeign来实现远程调用。
现在我们继续介绍一种新的方案,使用 RestTemplate
RestTemplate介绍
RestTemplate是Spring提供的一个用于访问RESTful服务的客户端工具,它从Spring 3.0开始支持。这个工具封装了常见的HTTP请求操作,如GET、POST、PUT、DELETE等,并简化了与RESTful服务的交互过程。
RestTemplate的主要作用包括:
- 发送HTTP请求:RestTemplate可以发送各种类型的HTTP请求,如GET、POST、PUT、DELETE等。
- 处理响应:RestTemplate可以接收响应结果并将其转换为对象、字符串等形式,方便开发者处理。
- 支持各种数据格式:RestTemplate可以处理JSON、XML等不同的数据格式,使其更加灵活和通用。
在使用RestTemplate时,首先需要创建RestTemplate的实例。
在Spring Boot中,可以通过在配置类中使用@Bean注解来创建RestTemplate实例,然后在需要使用RestTemplate的地方注入该实例。例如,可以使用getForEntity或getForObject方法来发送GET请求。
RestTemplate的优点包括支持连接池、超时时间设置、异步请求和响应的编解码等。
引入RestTemplate 依赖
在pom.xml
文件中(如果你使用的是Maven),确保你包含了spring-boot-starter-web
依赖:
<dependencies>
<!-- 其他依赖... -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 其他依赖... -->
</dependencies>
自定义配置
读取阿波罗配置
@Component
@ConfigurationProperties(prefix = "spring.http-client.pool")
@Data
public class HttpClientPoolConfig {
/**
* java配置的优先级低于yml配置;如果yml配置不存在,会采用java配置
*/
/**
* 连接池的最大连接数
*/
private int maxTotalConnect=1000 ;
/**
* 同路由的并发数
*/
private int maxConnectPerRoute=200 ;
/**
* 客户端和服务器建立连接超时,默认2s
*/
private int connectTimeout = 2 * 1000;
/**
* 指客户端从服务器读取数据包的间隔超时时间,不是总读取时间,默认30s
*/
private int readTimeout = 30 * 1000;
private String charset = "UTF-8";
/**
* 重试次数,默认2次
*/
private int retryTimes = 0;
/**
* 从连接池获取连接的超时时间,不宜过长,单位ms
*/
private int connectionRequestTimout = 200;
/**
* 针对不同的地址,特别设置不同的长连接保持时间,单位 s
*/
private int keepAliveTime = 60;
/**
* 针对不同的地址,特别设置不同的长连接保持时间
*/
private Map<String,Integer> keepAliveTargetHost;
}
连接池配置
utf-8编码等
@Configuration
@Component
@ConditionalOnClass(value = {RestTemplate.class, CloseableHttpClient.class})
@Data
@Slf4j
public class HttpClientConfig {
@Autowired
private HttpClientPoolConfig httpClientPoolConfig;
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
/**
* 创建HTTP客户端工厂
*/
@Bean(name = "clientHttpRequestFactory")
public ClientHttpRequestFactory clientHttpRequestFactory() {
/**
* maxTotalConnection 和 maxConnectionPerRoute 必须要配
*/
if (httpClientPoolConfig.getMaxTotalConnect() <= 0) {
throw new IllegalArgumentException("invalid maxTotalConnection: " + httpClientPoolConfig.getMaxTotalConnect());
}
if (httpClientPoolConfig.getMaxConnectPerRoute() <= 0) {
throw new IllegalArgumentException("invalid maxConnectionPerRoute: " + httpClientPoolConfig.getMaxConnectPerRoute());
}
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient());
// 连接超时
clientHttpRequestFactory.setConnectTimeout(httpClientPoolConfig.getConnectTimeout());
// 数据读取超时时间,即SocketTimeout
clientHttpRequestFactory.setReadTimeout(httpClientPoolConfig.getReadTimeout());
// 从连接池获取请求连接的超时时间,不宜过长,必须设置,比如连接不够用时,时间过长将是灾难性的
clientHttpRequestFactory.setConnectionRequestTimeout(httpClientPoolConfig.getConnectionRequestTimout());
return clientHttpRequestFactory;
}
/**
* 初始化RestTemplate,并加入spring的Bean工厂,由spring统一管理
*/
@Bean(name = "httpClientTemplate")
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return createRestTemplate(factory);
}
/**
* 配置httpClient
*
* @return
*/
@Bean
public HttpClient httpClient() {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
try {
//设置信任ssl访问
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build();
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();
//使用Httpclient连接池的方式配置(推荐),同时支持netty,okHttp以及其他http框架
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
// 最大连接数
poolingHttpClientConnectionManager.setMaxTotal(httpClientPoolConfig.getMaxTotalConnect());
// 同路由并发数
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(httpClientPoolConfig.getMaxConnectPerRoute());
//配置连接池
httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
// 重试次数
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(httpClientPoolConfig.getRetryTimes(), true));
//设置默认请求头
List<Header> headers = getDefaultHeaders();
httpClientBuilder.setDefaultHeaders(headers);
return httpClientBuilder.build();
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
log.info("初始化HTTP连接池出错", e);
}
return null;
}
/**
* 配置长连接保持策略
* @return
*/
public ConnectionKeepAliveStrategy connectionKeepAliveStrategy(){
return (response, context) -> {
// Honor 'keep-alive' header
HeaderElementIterator it = new BasicHeaderElementIterator(
response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
log.info("HeaderElement:{}", JSON.toJSONString(he));
String param = he.getName();
String value = he.getValue();
if (value != null && "timeout".equalsIgnoreCase(param)) {
try {
return Long.parseLong(value) * 1000;
} catch(NumberFormatException ignore) {
log.info("解析长连接过期时间异常",ignore);
}
}
}
HttpHost target = (HttpHost) context.getAttribute(
HttpClientContext.HTTP_TARGET_HOST);
//如果请求目标地址,单独配置了长连接保持时间,使用该配置
Optional<Map.Entry<String, Integer>> any = Optional.ofNullable(httpClientPoolConfig.getKeepAliveTargetHost()).orElseGet(HashMap::new)
.entrySet().stream().filter(
e -> e.getKey().equalsIgnoreCase(target.getHostName())).findAny();
//否则使用默认长连接保持时间
return any.map(en -> en.getValue() * 1000L).orElse(httpClientPoolConfig.getKeepAliveTime() * 1000L);
};
}
/**
* 设置请求头
*
* @return
*/
private List<Header> getDefaultHeaders() {
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"));
return headers;
}
private RestTemplate createRestTemplate(ClientHttpRequestFactory factory) {
RestTemplate restTemplate = new RestTemplate(factory);
//我们采用RestTemplate内部的MessageConverter
//重新设置StringHttpMessageConverter字符集,解决中文乱码问题
modifyDefaultCharset(restTemplate);
//设置错误处理器
restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
return restTemplate;
}
/**
* 修改默认的字符集类型为utf-8
*
* @param restTemplate
*/
private void modifyDefaultCharset(RestTemplate restTemplate) {
List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
HttpMessageConverter<?> converterTarget = null;
for (HttpMessageConverter<?> item : converterList) {
if (StringHttpMessageConverter.class == item.getClass()) {
converterTarget = item;
break;
}
}
if (null != converterTarget) {
converterList.remove(converterTarget);
}
Charset defaultCharset = Charset.forName(httpClientPoolConfig.getCharset());
converterList.add(1, new StringHttpMessageConverter(defaultCharset));
}
}