Feign 是一个声明式 Web服务客户端,使用它创建一个接口并注解,使得编写 Web服务客户端变得更加容易。
它支持可插拔注解,包括 Feign 注解和 JAX-RS 注解,还支持可插播得编码、解码器。
Cloud 增加了对 Spring MVC 注解的支持,默认使用 httpmessageconverter 的支持。
Cloud 集成了 Ribbon 和 Eureka以及 BalanceLoad,使得在使用 Feign 时支持 Http 客户端的负载均衡。
Feign 自定义的客户端配置不需要使用 @Configuration 配置注解。如果使用了,需要排除在 @ComponentScan 注解扫描之外。否则,它将成为 feign.Encoder、feign.Decoder、feign.Contract等组件的默认实现。如果指定了,可以在 @ComponentScan 中显示排除。
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
// basePackages 的别名
String[] value() default {};
//@FeignClient 注解组件扫描的基础包路径
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
//指定所有 feign 客户端的自定义配置.
Class<?>[] defaultConfiguration() default {};
//@FeignClient 注解的客户端类列表,不为空的话,关闭类路径的扫描
Class<?>[] clients() default {};
}
@EnableFeignClients 导入客户端注册类。
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//注册默认的配置
registerDefaultConfiguration(metadata, registry);
//注册客户端
registerFeignClients(metadata, registry);
}
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//忽略部分逻辑代码...
//@EnableFeignClients 无指定客户端类情况下,设置过滤筛选的注解类和获取扫描基础包路径
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
//加载@FeignClient 注解的属性集
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
//加载客户端名称,其 contextId 可覆盖 value属性
String name = getClientName(attributes);
//注册客户端配置
registerClientConfiguration(registry, name,attributes.get("configuration"));
//注册客户端实例Bean(重点)
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
//获取被注解的目标类型
String className = annotationMetadata.getClassName();
//生成 FeignClientFactoryBean bean的定义(重点)
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be null
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
从上述代码可以看到向容器中注册一个 FeignClientFactoryBean 类型的 bean 定义,由它生成最终目标接口的 bean 实例。
通过图解,我们知道 FeignClientFactoryBean 实现了 FactoryBean、InitializingBean,通过 FactoryBean 获取到自定义的Bean实例。
<T> T getTarget() {
//FeignContext工厂类,能够为每个feign客户端创建一个IOC子容器,并创建相关组件的实例
FeignContext context = this.applicationContext.getBean(FeignContext.class);
//创建Builder实例(构造Http API的工厂实例),设置相关组件配置(编码、解码器和拦截器(RequestInterceptor)等)
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
//path 不为空,追加到url后面,作为前缀
this.url += cleanPath();
//负载均衡
return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, this.url));
}
//忽略部分逻辑代码...
}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
//加载Feign 负载均衡客户端(LoadBalancerFeignClient)
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
//获取(HystrixTargeter)
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
}
public Feign build() {
//创建代理方法处理器的工厂实例
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy);
//创建请求方法 转 Rest请求的方法执行(MethodHandler) 解析处理实例
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
//创建feign 反射,实例化代理成目标接口实例
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
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;
}
public Map<String, MethodHandler> apply(Target key) {
//解析出方法的元数据
List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
//根据传递的参数方式,生成相应的模板构建器
//该构建器在请求调用期间会解析出请求的模板,
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder);
}
//创建目标接口方法的执行实例(SynchronousMethodHandler)
result.put(md.configKey(), factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
}
return result;
}
参数扩展点:在解析方法的元数据期间,我们可以自定义方法参数的处理器 (实现 AnnotatedParameterProcessor 接口),指定请求参数的位置。在构建请求模板的时候,可以通过 QueryMapEncoder 实现实例完成参数的解析、转换。
示例 @SpringQueryMap 注解结合 FieldQueryMapEncoder,完成 Get Http请求的 复合对象传参的解析、转换。
至此,通过JDK的动态代理机制,返回目标接口的代理实例。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//忽略部分代码逻辑
//通过分发器调用对应的方法执行实例
return dispatch.get(method).invoke(args);
}
@Override
public Object invoke(Object[] argv) throws Throwable {
//通过模板构建器,创建请求模板实例
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
//执行请求和解码
return executeAndDecode(template, options);
} catch (RetryableException e) {}
}
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
//忽略部分代码逻辑...
//应用请求拦截器(RequestInterceptor),创建请求实例
Request request = targetRequest(template);
Response response;
try {
response = client.execute(request, options);
} catch (IOException e) {
throw errorExecuting(request, e);
}
boolean shouldClose = true;
try {
if (Response.class == metadata.returnType()) {
if (response.body() == null) {
return response;
}
if (response.body().length() == null || response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false;
return response;
}
// Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
}
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
请求扩展点:在构造请求实例期间,可以通过实现 RequestInterceptor 接口,并结合配置文件(可参照FeignClientProperties配置属性)指定客户端实例的请求拦截器。
示例,可参照 BasicAuthRequestInterceptor。
通过上述请求模板的构建,生成请求实例后,进入请求调用阶段。
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
throw new RuntimeException(e);
}
}
通过 LoadBalancerFeignClient 实例调用 lbClient 方法,通过 CachingSpringLoadBalancerFactory 创建了 FeignLoadBalancer 负载均衡实例。
public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, ServerIntrospector serverIntrospector) {
super(lb, clientConfig);
this.setRetryHandler(RetryHandler.DEFAULT);
this.clientConfig = clientConfig;
//获取ribbon的配置,并作为默认的属性
this.ribbon = RibbonProperties.from(clientConfig);
RibbonProperties ribbon = this.ribbon;
this.connectTimeout = ribbon.getConnectTimeout();
this.readTimeout = ribbon.getReadTimeout();
this.serverIntrospector = serverIntrospector;
}
@Override
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException {
Request.Options options;
if (configOverride != null) {
//根据覆盖的配置,获取ribbon配置属性
RibbonProperties override = RibbonProperties.from(configOverride);
//若覆盖的属性不存在,则使用ribbon的配置属性
options = new Request.Options(override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {}
}
FeignLoadBalancer 实例初始化时,会加载 ribbon 的配置。最后在请求调用期间,若feign配置属性存在,则覆盖ribbon的配置属性。
public Observable<T> submit(final ServerOperation<T> operation) {
final ExecutionInfoContext context = new ExecutionInfoContext();
final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();
// Use the load balancer
Observable<T> o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
@Override
// Called for each server being selected
public Observable<T> call(Server server) {
// Called for each attempt and retry
Observable<T> o = Observable
.just(server)
.concatMap(new Func1<Server, Observable<T>>() {
return operation.call(server).doOnEach(new Observer<T>() {
});
}
});
if (maxRetrysSame > 0)
o = o.retry(retryPolicy(maxRetrysSame, true));
return o;
}
});
if (maxRetrysNext > 0 && server == null)
o = o.retry(retryPolicy(maxRetrysNext, false));
return ...
}
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {}
}
});
}
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
String host = null;
int port = -1;
// Various Supported Cases
// The loadbalancer to use and the instances it has is based on how it was registered
// In each of these cases, the client might come in using Full Url or Partial URL
ILoadBalancer lb = getLoadBalancer();
if (host == null) {
// Partial URI or no URI Case
// well we have to just get the right instances from lb - or we fall back
if (lb != null){
Server svc = lb.chooseServer(loadBalancerKey);
return svc;
}
} else {
// Full URL Case
// This could either be a vipAddress or a hostAndPort or a real DNS
// if vipAddress or hostAndPort, we just have to consult the loadbalancer
// but if it does not return a server, we should just proceed anyways
// and assume its a DNS
// For restClients registered using a vipAddress AND executing a request
// by passing in the full URL (including host and port), we should only
// consult lb IFF the URL passed is registered as vipAddress in Discovery
boolean shouldInterpretAsVip = false;
if (lb != null) {
shouldInterpretAsVip = isVipRecognized(original.getAuthority());
}
if (shouldInterpretAsVip) {
Server svc = lb.chooseServer(loadBalancerKey);
if (svc != null){
return svc;
}
}
}
return new Server(host, port);
}
至此,服务实例负载均衡筛选完毕。
接着进入请求执行,我们返回到 SynchronousMethodHandler ,它调用execute() 方法。
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}
通过 FeignClientProperties 可以配置重试,重试类在 FeignClientFactoryBean 中实例化,实例化的逻辑是 configureFeign 方法中,先从容器中查找 Retryer 中的 bean,如果有则填充到 Feign.Builder,再从 FeignClientProperties 中查找配置,如果有则再次填充 Feign.Builder,简单的讲就是覆盖逻辑。
protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = this.applicationContext.getBean(FeignClientProperties.class);
if (properties != null) {
if (properties.isDefaultToProperties()) {
//加载全局配置
configureUsingConfiguration(context, builder);
//加载默认配置
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
//加载指定客户端的配置
configureUsingProperties(properties.getConfig().get(this.contextId), builder);
}
else {
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.contextId), builder);
configureUsingConfiguration(context, builder);
}
}
else {
configureUsingConfiguration(context, builder);
}
}
而feign真正执行重试请求的逻辑在代理类 SynchronousMethodHandler 中,该类是JDK动态代理后,最终执行的方法处理器。
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
//循环,若调用无异常则立即返回;有异常,进入重试,计算重试时间间隔,通过线程休眠进行等待重新调用
while (true) {
try {
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
//这里重试,无异常则代表可以继续重试
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
从上述流程看出,若调用异常,就会通过 Retryer 重试处理器进入重试流程。
//默认实现
class Default implements Retryer {
//最大重试次数
private final int maxAttempts;
//重试周期
private final long period;
//最大重试周期
private final long maxPeriod;
//重试次数
int attempt;
long sleptForMillis;
public Default() {
this(100, SECONDS.toMillis(1), 5);
}
public Default(long period, long maxPeriod, int maxAttempts) {
this.period = period;
this.maxPeriod = maxPeriod;
this.maxAttempts = maxAttempts;
this.attempt = 1;
}
public void continueOrPropagate(RetryableException e) {
//重试次数 大于 最大重试次数,则抛出异常
if (attempt++ >= maxAttempts) {
throw e;
}
//计算重试的时间间隔
long interval;
if (e.retryAfter() != null) {
interval = e.retryAfter().getTime() - currentTimeMillis();
if (interval > maxPeriod) {
interval = maxPeriod;
}
if (interval < 0) {
return;
}
} else {
interval = nextMaxInterval();
}
try {
//线程阻塞、休眠
Thread.sleep(interval);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
throw e;
}
sleptForMillis += interval;
}
long nextMaxInterval() {
long interval = (long) (period * Math.pow(1.5, attempt - 1));
return interval > maxPeriod ? maxPeriod : interval;
}
@Override
public Retryer clone() {
return new Default(period, maxPeriod, maxAttempts);
}
}
//不重试实现(也是Feign默认的重试机制)
Retryer NEVER_RETRY = new Retryer() {
@Override
public void continueOrPropagate(RetryableException e) {
throw e;
}
@Override
public Retryer clone() {
return this;
}
};
我们知道 Spring Cloud 为 Feign 集成了 Ribbon ,提供了 Http 客户端的负载均衡支持。不仅自身提供了重试机制,也支持 Ribbon 的 重试机制。
在容器启动期间,根据类路径下是否有 spring-retry jar 包(即是有没 RetryTemplate 类型),创建了 RibbonLoadBalancedRetryFactory 工厂实例,用于在创建 Feign 客户端负载均衡时,创建可重试实例。
@Bean
@Primary
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
// LoadBalancedRetryFactory ,来源于 RibbonAutoConfiguration
return new CachingSpringLoadBalancerFactory(factory, retryFactory);
}
我们直接进入 Feign 执行重试请求的代理类 SynchronousMethodHandler。
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template);
Response response;
try {
//客户端执行请求,client 的实例是 LoadBalancerFeignClient 类型
response = client.execute(request, options);
} catch (IOException e) {
throw errorExecuting(request, e);
}
boolean shouldClose = true;
try {
if (Response.class == metadata.returnType()) {
if (response.body() == null) {
return response;
}
if (response.body().length() == null || response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false;
return response;
}
// Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
}
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
进入 LoadBalancerFeignClient 的 execute 方法查看到会调用 CachingSpringLoadBalancerFactory 创建 Feign 的负载均衡器。
public FeignLoadBalancer create(String clientName) {
FeignLoadBalancer client = this.cache.get(clientName);
if (client != null) {
return client;
}
IClientConfig config = this.factory.getClientConfig(clientName);
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
ServerIntrospector.class);
client = this.loadBalancedRetryFactory != null
? new RetryableFeignLoadBalancer(lb, config, serverIntrospector, this.loadBalancedRetryFactory)
: new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client);
return client;
}
RetryableFeignLoadBalancer 是 Feign 可重试负载均衡客户端实例,利用了 spring retry 去重试失败的请求。类图如下:
在创建好实例之后,执行 executeWithLoadBalancer 方法,它在 AbstractLoadBalancerAwareClient 父类中,最终还是调用子类实例的 execute 方法。
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
//构造负载均衡的指令
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
//执行请求
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {}
}
})
.toBlocking()
.single();
} catch (Exception e) {}
}
@Override
public RibbonResponse execute(final RibbonRequest request, IClientConfig configOverride) throws IOException {
final Request.Options options;
if (configOverride != null) {
//加载、设置ribbon的承诺书配置
RibbonProperties ribbon = RibbonProperties.from(configOverride);
options = new Request.Options(ribbon.connectTimeout(this.connectTimeout), ribbon.readTimeout(this.readTimeout));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
//创建重试策略
final LoadBalancedRetryPolicy retryPolicy = this.loadBalancedRetryFactory.createRetryPolicy(this.getClientName(), this);
//创建重试模板
RetryTemplate retryTemplate = new RetryTemplate();
//创建回退策略
BackOffPolicy backOffPolicy = this.loadBalancedRetryFactory.createBackOffPolicy(this.getClientName());
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
//创建重试监听器
RetryListener[] retryListeners = this.loadBalancedRetryFactory.createRetryListeners(this.getClientName());
if (retryListeners != null && retryListeners.length != 0) {
retryTemplate.setListeners(retryListeners);
}
//根据条件是否开启重试策略,或者代理
retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy()
: new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this,this.getClientName()));
return retryTemplate.execute(new RetryCallback<RibbonResponse, IOException>() {
//重试执行回调
@Override
public RibbonResponse doWithRetry(RetryContext retryContext)throws IOException {
Request feignRequest = null;
// on retries the policy will choose the server and set it in the context
// extract the server and update the request being made
if (retryContext instanceof LoadBalancedRetryContext) {
ServiceInstance service = ((LoadBalancedRetryContext) retryContext).getServiceInstance();
if (service != null) {
feignRequest = ((RibbonRequest) request
.replaceUri(reconstructURIWithServer(new Server(service.getHost(), service.getPort()), request.getUri()))).toRequest();
}
}
if (feignRequest == null) {
feignRequest = request.toRequest();
}
Response response = request.client().execute(feignRequest, options);
if (retryPolicy != null && retryPolicy.retryableStatusCode(response.status())) {
byte[] byteArray = response.body() == null ? new byte[] {}
: StreamUtils.copyToByteArray(response.body().asInputStream());
response.close();
throw new RibbonResponseStatusCodeException(RetryableFeignLoadBalancer.this.clientName, response,byteArray, request.getUri());
}
return new RibbonResponse(request.getUri(), response);
}
}, new LoadBalancedRecoveryCallback<RibbonResponse, Response>() {
//兜底执行回调
@Override
protected RibbonResponse createResponse(Response response, URI uri) {
return new RibbonResponse(uri, response);
}
});
}
引用
官方文档
Spring Cloud的 Feign和Ribbon重试机制的误区
配置Feign重试机制