高版本 OpenFeign 自定義配置 httpclient 以及配置 fastJson

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章