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