openFeign 使用
其实就算不用springcloud 也是可以 在spring boot 中使用 OpenFeign 的,
使用OpenFeign 来编写 接口这种方式,确实挺轻量,而且以于维护, 都可以放弃使用restTemplate 了,而且以后升级微服务也可能更快。
spring boot 配置使用
低版本OpenFeign的配置 httpClient方式
@Bean
public CloseableHttpClient httpClient() {
return HttpClients.custom()
.setConnectionTimeToLive(30, TimeUnit.SECONDS)
.evictIdleConnections(30, TimeUnit.SECONDS)
.setMaxConnTotal(200)
.setMaxConnPerRoute(20)
.disableAutomaticRetries()
.setKeepAliveStrategy(new CustomConnectionKeepAliveStrategy())
.build();
}
参考配置
以上方式 去百度 都是这种方式。。。
高版本 spring cloud OpenFeign 自定义配置 httpclient
目前我使用的版本是 <spring-cloud.version>Greenwich.SR3</spring-cloud.version>
按照低版本的配置方式 结果发现并没有生效,使用的是原生JAVA方式发送请求的,
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import javax.annotation.PreDestroy;
import feign.Client;
import feign.Feign;
import feign.httpclient.ApacheHttpClient;
import feign.okhttp.OkHttpClient;
import okhttp3.ConnectionPool;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Spencer Gibb
* @author Julien Roy
*/
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
FeignHttpClientProperties.class })
public class FeignAutoConfiguration {
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);
}
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
@Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
@Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
// the following configuration is for alternate feign clients if
// ribbon is not on the class path.
// see corresponding configurations in FeignRibbonClientAutoConfiguration
// for load balanced ribbon clients.
@Configuration
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignConfiguration {
private final Timer connectionManagerTimer = new Timer(
"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);
@Autowired(required = false)
private RegistryBuilder registryBuilder;
private CloseableHttpClient httpClient;
@Bean
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
public HttpClientConnectionManager connectionManager(
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
FeignHttpClientProperties httpClientProperties) {
final HttpClientConnectionManager connectionManager = connectionManagerFactory
.newConnectionManager(httpClientProperties.isDisableSslValidation(),
httpClientProperties.getMaxConnections(),
httpClientProperties.getMaxConnectionsPerRoute(),
httpClientProperties.getTimeToLive(),
httpClientProperties.getTimeToLiveUnit(),
this.registryBuilder);
this.connectionManagerTimer.schedule(new TimerTask() {
@Override
public void run() {
connectionManager.closeExpiredConnections();
}
}, 30000, httpClientProperties.getConnectionTimerRepeat());
return connectionManager;
}
@Bean
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
HttpClientConnectionManager httpClientConnectionManager,
FeignHttpClientProperties httpClientProperties) {
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
.build();
this.httpClient = httpClientFactory.createBuilder()
.setConnectionManager(httpClientConnectionManager)
.setDefaultRequestConfig(defaultRequestConfig).build();
return this.httpClient;
}
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(HttpClient httpClient) {
return new ApacheHttpClient(httpClient);
}
@PreDestroy
public void destroy() throws Exception {
this.connectionManagerTimer.cancel();
if (this.httpClient != null) {
this.httpClient.close();
}
}
}
}
}
通过上面的代码可以知 @ConditionalOnMissingBean(CloseableHttpClient.class)
才会生效的
那么如果 要定义 怎么才能生效? 通过代码可以知, 再自定义 Client 即可 。
/**
* 定义 client 获取的方式,这里是 httpClient
*
* @param httpClient
* @return
*/
@Bean
public Client feignClient(HttpClient httpClient) {
return new ApacheHttpClient(httpClient);
}
因为 openFeign 是 通过 Client 接口 持有 实现来实现 远程调用的。
高版本 OpenFeign 自定义配置 httpclient 以及配置 fastJson 完整配置代码
fastjson :
public static StringHttpMessageConverter getStringHttpMessageConverter() {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
// 解决Controller的返回值为String时,中文乱码问题
stringConverter.setDefaultCharset(Charset.forName("UTF-8"));
return stringConverter;
}
public static FastJsonHttpMessageConverter getFastJsonHttpMessageConverter() {
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(
// 防止循环引用
SerializerFeature.DisableCircularReferenceDetect,
// 空集合返回[],不返回null
SerializerFeature.WriteNullListAsEmpty,
// 空字符串返回"",不返回null
// SerializerFeature.WriteNullStringAsEmpty,
SerializerFeature.WriteMapNullValue);
fastJsonConfig.setDateFormat(DatePattern.NORM_DATETIME_PATTERN); // 时间格式
fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
// 支持的类型。 虽然默认 已经指出全部类型了,但是还是指定fastjson 只用于json格式的数据,避免其他格式数据导致的异常
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fastMediaTypes.add(MediaType.APPLICATION_JSON);
fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
fastJsonHttpMessageConverter.setDefaultCharset(Charset.forName("UTF-8"));
return fastJsonHttpMessageConverter;
}
openFeign 配置
package com.door.config;
import feign.Client;
import feign.httpclient.ApacheHttpClient;
import org.apache.http.client.HttpClient;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import java.util.List;
/** 配置openFeign json 格式化 ; stringConverter 加上只是为了防止中文乱码 */
@Configuration
public class OpenFeignConfig {
// 其实 这个解析不用配置也是可以的。因为只是解析 而已,用 jackJSON 一般没问题的
@Bean
public ResponseEntityDecoder feignDecoder() {
HttpMessageConverter fastJsonConverter = FastJSONConfig.getFastJsonHttpMessageConverter();
StringHttpMessageConverter stringConverter = FastJSONConfig.getStringHttpMessageConverter();
ObjectFactory<HttpMessageConverters> objectFactory =
() -> new HttpMessageConverters(stringConverter, fastJsonConverter);
return new ResponseEntityDecoder(new SpringDecoder(objectFactory));
}
@Bean
public SpringEncoder feignEncoder(List<HttpMessageConverter<?>> converters) {
// 注意 fastJsonConverter 必须指定 MediaType 否则会报错
HttpMessageConverter fastJsonConverter = FastJSONConfig.getFastJsonHttpMessageConverter();
StringHttpMessageConverter stringConverter = FastJSONConfig.getStringHttpMessageConverter();
ObjectFactory<HttpMessageConverters> objectFactory =
() -> new HttpMessageConverters(stringConverter, fastJsonConverter);
return new SpringEncoder(objectFactory);
}
/**
* 定义 client 获取的方式,这里是 httpClient
*
* @param httpClient
* @return
*/
@Bean
public Client feignClient(HttpClient httpClient) {
return new ApacheHttpClient(httpClient);
}
}
自定义httpClient
/**
* http链接池 服务器返回数据(response)的时间,超过该时间抛出read timeout
*
* @return
*/
@Bean
public CloseableHttpClient httpClientMy()
throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
Registry<ConnectionSocketFactory> registry =
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
// 这样也可以忽略证书
// .register("https", getNoValidSslConnectionSocketFactory())
.build();
PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager(registry);
// 设置整个连接池最大连接数 根据自己的场景决定
int maxPoolSize = Runtime.getRuntime().availableProcessors() * 2;
connectionManager.setMaxTotal(maxPoolSize * 8);
// 路由是对maxTotal的细分
// 分配给同一个route(路由)最大的并发连接数
connectionManager.setDefaultMaxPerRoute(maxPoolSize);
RequestConfig requestConfig =
RequestConfig.custom()
.setSocketTimeout(15000) // 服务器返回数据(response)的时间,超过该时间抛出read timeout
.setConnectTimeout(15000) // 连接上服务器(握手成功)的时间,超出该时间抛出connect timeout
.setConnectionRequestTimeout(
3000) // 从连接池中获取连接的超时时间,超过该时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
.build();
return HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(connectionManager)
.build();
}
/**
* 获取 不验证SSL的 SSL连接器
*
* @return
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
* @throws KeyStoreException
*/
private SSLConnectionSocketFactory getNoValidSslConnectionSocketFactory()
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException {
TrustStrategy acceptingTrustStrategy = (x509Certificates, authType) -> true;
SSLContext sslContext =
SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
return new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
}
参考资料
https://www.jianshu.com/p/b3b93a32cb05
https://blog.csdn.net/lppl010_/article/details/94215233