SpringCloud OpenFeign使用OkHttp,添加响应拦截器

前言

SpringCloud微服务之间的请求一般使用OpenFeign,有时候我们需要在请求或者响应的时候做一些额外的操作。比如请求的时候添加请求头,响应的时候判断token是否过期等等。这时候拦截器就派上用场了!我们接下来就说一下怎么添加请求和响应拦截器。

一、修改OpenFeign的http客户端

OpenFeign默认的http客户端是javax.net.ssl.HttpsURLConnection,详细信息见feign-core:feign.Client,该http客户端不支持添加拦截器和连接池。所以我们需要添加第三方http客户端,可选的http客户端有httpClient和okHttp,对应的依赖如下:

<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-httpclient</artifactId>
  <version>9.7.0</version>
</dependency>
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
  <version>9.7.0</version>
</dependency>

OpenFeign默认启用的是HttpClient,但是我使用的是OkHttp,故添加以下配置

feign.httpclient.enabled=false
feign.okhttp.enabled=true

添加以上配置后,OpenFeign的http客户端就自动切换为OkHttp了,详细过程看源码就清楚了,org.springframework.cloud.openfeign.FeignAutoConfiguration,org.springframework.cloud.openfeign.ribbon.OkHttpFeignLoadBalancedConfiguration。在此就不赘述了。

二、添加请求拦截器

OpenFeign官方自带请求拦截器的接口feign.RequestInterceptor,我们只要实现该接口就可以了。然后你就可以做你想做的任何事情了

@Component
public class CustomerFeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            template.header("sessionId", request.getHeader("sessionId"));
        }
    }
}

三、添加响应拦截器

刚开始我以为OpenFeign官方有响应拦截器的接口,但是我发翻了半天源码也没找到,所以就想在OkHttp上打主意。

网上很多文章里的办法都是重新构造OkHttpClient的Bean,

但是通过查看源码org.springframework.cloud.openfeign.FeignAutoConfiguration发现有这个配置:@ConditionalOnMissingBean(okhttp3.OkHttpClient.class),大概意思就是如果没有OkHttpClient这个Bean,就启动这个配置,如果存在OkHttpCLient的Bean,则不启用这个配置。如果我们自定义OkHttpClient的Bean,将导致该配置不能生效,我们可能就需要添加很多配置,这简直就是费力不讨好嘛,那有没有更好的方法呢??肯定是有的!!

	@Configuration
	@ConditionalOnClass(OkHttpClient.class)
	@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
	@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
	@ConditionalOnProperty(value = "feign.okhttp.enabled")
	protected static class OkHttpFeignConfiguration {

		private okhttp3.OkHttpClient okHttpClient;

		@Bean
		@ConditionalOnMissingBean(ConnectionPool.class)
		public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
													   OkHttpClientConnectionPoolFactory connectionPoolFactory) {
			Integer maxTotalConnections = httpClientProperties.getMaxConnections();
			Long timeToLive = httpClientProperties.getTimeToLive();
			TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
			return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
		}

		@Bean
		public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
										   ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
			Boolean followRedirects = httpClientProperties.isFollowRedirects();
			Integer connectTimeout = httpClientProperties.getConnectionTimeout();
			Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
			this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation).
					connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).
					followRedirects(followRedirects).
					connectionPool(connectionPool).build();
			return this.okHttpClient;
		}

		@PreDestroy
		public void destroy() {
			if(okHttpClient != null) {
				okHttpClient.dispatcher().executorService().shutdown();
				okHttpClient.connectionPool().evictAll();
			}
		}

		@Bean
		@ConditionalOnMissingBean(Client.class)
		public Client feignClient(okhttp3.OkHttpClient client) {
			return new OkHttpClient(client);
		}
	}

我们仔细观察OkHttpFeignConfiguration这个内部类里是怎么配置OkHttpClient的,发现是通过Builer建造者模式创建的对象,OkHttpClient.Builder又是通过OkHttpClientFactory接口来生成的,OkHttpClientFactory接口的默认实现DefaultOkHttpClientFactory的构造参数又是OkHttpClient.Builder。

还有很重要的一点,OkHttpClientFactory在FeignAutoConfiguration整个类中并没有实例化,只有在方法参数中引用了,这说明OkHttpClientFactory在其他地方已经实例化了,已经是spring管理的Bean了。接下来我们要找到OkHttpClientFactory是在哪里实例化的。在Idea中打开OkHttpClientFactory这个接口的代码,按住Ctrl加鼠标左键,出现以下内容

一共有7个地方引用了OkHttpClientFactory,但是只有HttpClientConfiguration是我们没有接触过的,其他的上面都有涉及。所以OkHttpClientFactory很有可能就是在这个类里实例化的,打开这个类,果然如此!!

前面说到,OkHttpClient的实例化和OkHttpClient.Builder、OkHttpClientFactory有关,OkHttpClientFactory的实例化又和OkHttpClient.Builder有关。所以,我们只要自定义OkHttpClient.Builder就能完美解决这个问题!!自定义OkHttpClient.Builder的时候添加响应拦截器就可以了。切记重新构造Response返回,如果直接返回Response,会导致Feign直接走降级。

@Slf4j
@Configuration
public class FeignOkHttpClientConfig {

    @Bean
    public OkHttpClient.Builder okHttpClientBuilder() {
        return new OkHttpClient.Builder().addInterceptor(new FeignOkHttpClientResponseInterceptor());
    }

    /**
     * okHttp响应拦截器
     */
    public static class FeignOkHttpClientResponseInterceptor implements Interceptor{


        @Override
        public Response intercept(Chain chain) throws IOException {

            Request originalRequest = chain.request();
            Response response = chain.proceed(originalRequest);
          
            MediaType mediaType = response.body().contentType();
            String content = response.body().string();
            
            //解析content,做你想做的事情!!

            //生成新的response返回,网络请求的response如果取出之后,直接返回将会抛出异常
            return response.newBuilder()
                    .body(ResponseBody.create(mediaType, content))
                    .build();
        }
    }

}

写在最后的话

刚开始我也是直接复制网上的方法,发现不太好用,然后花了一点时间看了下源码,找到了解决方法。所以有时候直接百度也不太好用,还是得靠自己。

猜你喜欢

转载自blog.csdn.net/WayneLee0809/article/details/111474951
今日推荐