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