SpringCloud微服务之间的请求一般使用OpenFeign,有时候我们需要在请求或者响应的时候做一些额外的操作。比如请求的时候添加请求头,响应的时候判断token是否过期等等。这时候拦截器就派上用场了!我们接下来就说一下怎么添加请求和响应拦截器。
OpenFeign默认的http客户端是javax.net.ssl.HttpsURLConnection,详细信息见feign-core:feign.Client,该http客户端不支持添加拦截器和连接池。所以我们需要添加第三方http客户端,可选的http客户端有httpClient和okHttp,对应的依赖如下:
io.github.openfeign
feign-httpclient
9.7.0
io.github.openfeign
feign-okhttp
9.7.0
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();
}
}
}
刚开始我也是直接复制网上的方法,发现不太好用,然后花了一点时间看了下源码,找到了解决方法。所以有时候直接百度也不太好用,还是得靠自己。