转载至:
https://blog.csdn.net/forezp/article/details/73480304
本文出自方志朋的博客
出自方志朋的博客
1.首先定义一个接口,然后在接口上面加上注解@FeignClient, 然后其中的注解参数value为服务名称
2. FeignClient的配置类 FeignClientsConfiguration, 配置了Feign.Builder, HystrixFeign.Builder等等
可以重写这个类,达到自定义配置的目的
这个类中标明的默认重试次数为Retryer.NEVER_RETRY,即不重试,那么希望做到重写,写个配置文件,
注入feignRetryer的 bean,代码如下
@Configuration
public class FeignConfig {
@Bean
public Retryer feignRetryer() {
return new Retryer.Default(100, SECONDS.toMillis(1), 5);
}
}
在上述代码更改了该FeignClient的重试次数,重试间隔为100ms,最大重试时间为1s,重试次数为5次。
3.Feign的工作原理:
① feign通过处理注解的方式替换掉request模板中的参数,这种实现方式显得更为直接、可理解。
程序启动后通过包扫描,当类有@FeignClient注解,将注解的信息取出,连同类名一起取出,赋给BeanDefinitionBuilder,
最后注入到ioc容器里面
② 注入bean之后,通过jdk的代理,当请求Feign Client的方法时会被拦截,代码在ReflectiveFeign类,代码如下:
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if(Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
④在LoadBalancerFeignClient里面,通过ribbon组件的负载均衡算法,从注册中心ip的list中,获取到一个ip,拼接成ip接口的形式.
⑤最后在SynchronousMethodHandler类进行拦截处理,当被FeignClient的方法被拦截会根据参数生成RequestTemplate对象,
该对象就是http请求的模板,代码如下:
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
其中有个executeAndDecode()方法,该方法是通RequestTemplate生成Request请求对象,然后根据用client获取response。
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = targetRequest(template);
...//省略代码
response = client.execute(request, options);
...//省略代码
}
⑥Client组件
其中Client组件是一个非常重要的组件,Feign最终发送request请求以及接收response响应,都是由Client组件完成的,其中Client的实现类,只要有Client.Default,该类由HttpURLConnnection实现网络请求,另外还支持HttpClient、Okhttp.
首先来看以下在FeignRibbonClient的自动配置类,FeignRibbonClientAutoConfiguration ,主要在工程启动的时候注入一些bean,其代码如下:
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection).toBuilder().request(request).build();
}
在缺失配置feignClient的情况下,会自动注入new Client.Default(),跟踪Client.Default()源码,它使用的网络请求框架为HttpURLConnection,代码如下:
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection).toBuilder().request(request).build();
}
4. 怎么在feign中使用HttpClient ?
① 引入httpclient的jar包
<!--feign使用httpclient--> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency> <!--feign 依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
可以看到在openfeign中io.git.openfeign.feign-core中并没实现,所以引入的feign-httpclient中正好有这个核心包
然后他只有一个类ApacheHttpClient实现client接口,这个类中又注入了一个httpclient,所以可以通过自定义http连接池,注入到fegin请求中.但是要注意的是, 他的请求模板中又注入一个Options类,这个类中有两个参数connectTimeoutMillis,readTimeoutMillis
分别是连接超时时间和读超时时间,所以说自定义的httpclient中的超时时间没有用,只有连接池有用,
所以这个两个超时时间需要单独配置.
feign.client.config.default.connect-timeout=6000 feign.client.config.default.read-timeout=6000