<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.0.2</version>
</dependency>
0.本文重点
- FeignClient创建代理对象过程
- FeignClient执行调用过程
- FeignClient如何给每个client单独配置超时时间
- 当发生404,502,503时如何使用Retry
- 引申:使用Supplier和FactoryBean的区别
1.注册beanDefinition
EnableFeignClients
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}
FeignClientsRegistrar#registerBeanDefinitions
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
}
在前文spring ioc处理机制一文中我们知道@Import注解声明的ImportBeanDefinitionRegistrar会在ConfigurationClassPostProcessor#processConfigBeanDefinitions扫描beanDefinition时回调其registerBeanDefinitions方法。
FeignClientsRegistrar#registerFeignClients
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
//获取类路径扫描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
/添加扫描 注解过滤的filter 代表 扫描类时我只要 带FeignClient注解的
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
//声明了多个包路径 一个个扫
for (String basePackage : basePackages) {
//内部调用ClassPathScanningCandidateComponentProvider#scanCandidateComponents返回符合要求
//的beanDefinition
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
//指定class 类型,只实例化相应的clients,我们一般都是直接指定包路径
for (Class<?> clazz : clients) {
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
//强制要求必须为接口
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
//获取注解元信息
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes( FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//基于注解元信息 改造beandefition definition中的参数决定了spring如何实例化bean,
//采用何种方式,有什么样的初始值,使用何种方式注入依赖等
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
FeignClientsRegistrar#getScanner
protected ClassPathScanningCandidateComponentProvider getScanner() {
//Spring提供的这个类 可以帮助我们扫描classPath下类信息 重要:我们可以合理的运用这个类 来自定义自己的框架
//这里复写了 isCandidateComponent方法 声明扫出来的类不能是一个注解
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
@Override
//父类的实现是必须为非接口或非抽象类(如果为抽象类必须有包含@Lookup注解的方法)
protected boolean isCandidateComponent(
AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent()) {
//非注解
if (!beanDefinition.getMetadata().isAnnotation()) {
isCandidate = true;
}
}
return isCandidate;
}
};
}
ClassPathScanningCandidateComponentProvider#scanCandidateComponents
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
//classpath*:xxx/xx/xx/**/*.class
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
//扫描出来的文件资源
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
...
for (Resource resource : resources) {
...
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
//这里除了之前声明的只扫描带FeignClient注解的类 还会进行Conditional 通用判断
if (isCandidateComponent(metadataReader)) {
//继承自 GenericBeanDefinition ,并实现了 AnnotatedBeanDefinition 接口
//通常这类definition用于 描述标注 @Component 注解的 Bean
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
//这里就是上面重写的方法了
if (isCandidateComponent(sbd)) {
candidates.add(sbd);
}
.....
return candidates;
}
FeignClientsRegistrar#registerFeignClient
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//获取原始的类 类型
Class clazz = ClassUtils.resolveClassName(className, null);
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
String contextId = getContextId(beanFactory, attributes);
//获取client服务名 内部可基于beanFactory.resolveEmbeddedValue 解析 ${}占位符
String name = getName(attributes);
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
//这里使用了Supplier的方式,beanDefinition如果绑定了Supplier,
//在AbstractAutowireCapableBeanFactory#createBeanInstance创建bean时使用Supplier#get方法,
//而不是factoryMethod或构造器
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
//同上getName 内部可基于beanFactory.resolveEmbeddedValue 解析 ${}占位符
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
//一般情况下如果对方接口抛异常404时,而又没有catch时,前端会收到500错误 ,decode404会将404会返回前端404错误信息
factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
//fallback配置
...
return factoryBean.getObject();
});
//spring依赖处理篇有介绍相关AutowireMode,AbstractBeanDefinition.AUTOWIRE_BY_TYPE比AUTOWIRE_BY_NAME更严格
//AUTOWIRE_BY_NAME找不到相关依赖不会报错
definition.setAutowireMode(AbstractBeanDefAbstractBeanDefinition.AUTOWIRE_BY_TYPE比NAMEinition.AUTOWIRE_BY_TYPE);
definition.setLazyInit(true);
//校验fallback不能为接口 否则就不能回调了
validate(attributes);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
...
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
//注册beanDefinition
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
在历史版本中,上面registerBeanDefinition注册的是FeignClientFactoryBean,使用
definition.addPropertyValue("url", getUrl(attributes)property为FeignClientFactoryBean注入参数。 其实现过程是先实例化FeignClientFactoryBean,继而在其他类引用client时才调用FactoryBean.getObject方法来实例化真正的client.新版使用Supplier调整了实例化真正的client的流程 , 实例化FactoryBean的过程也会历经populate,initializeBean阶段,而这几个阶段对于这个场景的FeignClientFactoryBean没有意义。并且FactoryBean创建的bean 没有populate阶段 不会进行属性注入,只有initializeBean中的 BeanPostProcessor#postProcessAfterInitialization 阶段。调整为Supplier的方式使得client变得更像一个普通的bean,开发者可以直接在client使用@Autowired @Value @PostConstruct等注解了
接下来我们看看FeignClientFactoryBean是如何创建真正的client的
2.FeignClientFactoryBean创建client
FeignClientFactoryBean#getTarget
<T> T getTarget() {
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
: applicationContext.getBean(FeignContext.class);
//创建 Feign.Builder,并读取容器中配置,先使用默认配置为Builder赋值:连接、超时时间,
//RequestInterceptor拦截器,Retryer等。
//再尝试根据服务名查找独有配置。
Feign.Builder builder = feign(context);
//url一般只在本地调试阶段使用 就是指向固定的ip 端口,所以非调试阶段url一般为空
if (!StringUtils.hasText(url)) {
if (!name.startsWith("http")) {
//拼接服务名 http://order-serice
url = "http://" + name;
}
else {
url = name;
}
//拼接根路径 http://order-serice/order
url += cleanPath();
//使用负载均衡的形式
return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
}
//下面的就是指定ip 端口的固定请求了。有兴趣的可以自己看看
.....
}
FeignClientFactoryBean#feign
protected Feign.Builder feign(FeignContext context) {
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
//解析方法元信息
.contract(get(context, Contract.class));
//读取配置信息
configureFeign(context, builder);
//基于 FeignBuilderCustomizer 定制化配置 在这里你想干嘛就干嘛。。。
applyBuildCustomizers(context, builder);
return builder;
}
FeignClientFactoryBean#configureFeign
protected void configureFeign(FeignContext context, Feign.Builder builder) {
//feign.client这个里面配置的东西
FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class)
: applicationContext.getBean(FeignClientProperties.class);
if (properties != null && inheritParentContext) {
if (properties.isDefaultToProperties()) {
//一般走这里 先配置默认参数 再配置default里的 再配置服务名下的配置 每一级都会判空,然后覆盖
configureUsingConfiguration(context, builder);
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(contextId), builder);
}
else {
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(contextId), builder);
configureUsingConfiguration(context, builder);
}
}
else {
configureUsingConfiguration(context, builder);
}
}
FeignBuilderCustomizer
//为每个不同的服务注册 FeignBuilderCustomizer可以这么用。
当然 上面的configureFeign中其他所有参数形如 Request.Options,ErrorDecoder
//都可以通过注解引入一个配置类的形式做到配置隔离
@FeignClient(value = "order",configuration = OrderConfig.class)
public interface OrderClient extends InitializingBean {
....
}
public class OrderConfig {
@Bean
public FeignBuilderCustomizer feignBuilderCustomizer(){
return builder -> {
builder.decode404();
};
}
}
FeignClientProperties
@ConfigurationProperties("feign.client")
public class FeignClientProperties {
private boolean defaultToProperties = true;
private String defaultConfig = "default";
private Map<String, FeignClientConfiguration> config = new HashMap<>();
private boolean decodeSlash = true;
public static class FeignClientConfiguration {
private Logger.Level loggerLevel;
private Integer connectTimeout;
private Integer readTimeout;
private Class<Retryer> retryer;
private Class<ErrorDecoder> errorDecoder;
private List<Class<RequestInterceptor>> requestInterceptors;
private Map<String, Collection<String>> defaultRequestHeaders;
private Map<String, Collection<String>> defaultQueryParameters;
private Boolean decode404;
}
}
feign:
httpclient:
enabled: true
client:
config:
default:
#建立连接所用的时间,适用于网络状况正常的情况下,两端连接所需要的时间
connectTimeout: 10000
#指建立连接后从服务端读取到可用资源所用的时间
order:
ConnectTimeOut: 30000
retryer: feign.Retryer.Default
HardCodedTarget
public static class HardCodedTarget<T> implements Target<T> {
//声明@FeignClient的接口
private final Class<T> type;
//服务名
private final String name;
// http://服务名/根路径
private final String url;
}
FeignClientFactoryBean#loadBalance
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
//根据环境返回实例 笔者这里返回 FeignBlockingLoadBalancerClient
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
//和 DefaultTargeter
Targeter targeter = get(context, Targeter.class);
//DefaultTargeter.target方法 继而调用 Feign.Builder.target方法,传入HardCodedTarget
return targeter.target(this, builder, context, target);
}
...
}
Feign.Builder#target
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public Feign build() {
Client client = Capability.enrich(this.client, capabilities);
Retryer retryer = Capability.enrich(this.retryer, capabilities);
List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
.map(ri -> Capability.enrich(ri, capabilities))
.collect(Collectors.toList());
Logger logger = Capability.enrich(this.logger, capabilities);
Contract contract = Capability.enrich(this.contract, capabilities);
Options options = Capability.enrich(this.options, capabilities);
Encoder encoder = Capability.enrich(this.encoder, capabilities);
Decoder decoder = Capability.enrich(this.decoder, capabilities);
//InvocationHandlerFactory.Default
InvocationHandlerFactory invocationHandlerFactory =
Capability.enrich(this.invocationHandlerFactory, capabilities);
QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
//最终将各类配置组装成 ReflectiveFeign对象
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
feign.ReflectiveFeign#newInstance
//target是包含类信息 url 服务名的 HardCodedTarget
public <T> T newInstance(Target<T> target) {
//解析方法元信息 为每个方法创建不同的MethodHandler MethodHandler可以理解为方法的代理执行对象
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
//建立方法 与 MethodHandler 映射关系
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)) {
//default方法创建 DefaultMethodHandler,其invoke 方法就是直接调用原始default方法
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//创建 ReflectiveFeign.FeignInvocationHandler 传入HardCodedTarget方法 与MethodHandler 映射关系
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
//默认方法handler绑定到代理对象
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
至此feignClient代理对象创建完毕,代理对象的InvocationHandler 其内部持有方法与MethodHandler的映射关系,MehthodHandler,MethodHandler解析了方法的元信息,包含配置信息和参数类型,参数解析器等。
ParseHandlersByName#apply
public Map<String, MethodHandler> apply(Target target) {
//基于SpringMvcContract解析类中的方法 获取方法元信息MethodMetadata
List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
//formdata
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
buildTemplate =
new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else if (md.bodyIndex() != null) {
//json
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else {
//query
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
}
if (md.isIgnored()) {
..
} else {
//创建MethodHandler (SynchronousMethodHandler)
result.put(md.configKey(),
factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
}
}
return result;
}
}
SpringMvcContract#parseAndValidateMetadata
public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
//根据方法名 方法参数 作key
processedMethods.put(Feign.configKey(targetType, method), method);
//下方BaseContract#parseAndValidateMetadata
MethodMetadata md = super.parseAndValidateMetadata(targetType, method);
RequestMapping classAnnotation = findMergedAnnotation(targetType, RequestMapping.class);
...组合类合方法的 accept consume headers属性
return md;
}
BaseContract#parseAndValidateMetadata
protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
final MethodMetadata data = new MethodMetadata();
data.targetType(targetType);
data.method(method);
data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));
data.configKey(Feign.configKey(targetType, method));
...
//提取类上的RequestMapping注解 value值 作为 data.template().uri()值
processAnnotationOnClass(data, targetType);
//提取方法上的RequestMapping注解属性 并存入到 data.template()中
for (final Annotation methodAnnotation : method.getAnnotations()) {
processAnnotationOnMethod(data, methodAnnotation, method);
}
....
final Class<?>[] parameterTypes = method.getParameterTypes();
final Type[] genericParameterTypes = method.getGenericParameterTypes();
final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
final int count = parameterAnnotations.length;
for (int i = 0; i < count; i++) {
boolean isHttpAnnotation = false;
if (parameterAnnotations[i] != null) {
//基于RequestPartParameterProcessor,RequestHeaderParameterProcessor,RequestParamParameterProcessor,
PathVariableParameterProcessor等 判断isHttpAnnotation,同时标记
//同时根据ConversionService判断是否支持类型转换,并标记位置index
isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
}
if (isHttpAnnotation) {
//基于BitSet 忽略下标
data.ignoreParamater(i);
}
if (parameterTypes[i] == URI.class) {
//服务名可在方法参数中动态传入 后续的调用时会将uri和方法requestmapping中路径拼接起来
data.urlIndex(i);
} else if (!isHttpAnnotation && parameterTypes[i] != Request.Options.class) {
if (data.isAlreadyProcessed(i)) {
...
} else {
...
data.bodyIndex(i);
data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));
}
}
}
....
return data;
}
3. ReflectiveFeign.FeignInvocationHandler#invoke feignClient调用过程
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
..equals hash toString 方法..
} else if ("toString".equals(method.getName())) {
return toString();
}
return dispatch.get(method).invoke(args);
}
SynchronousMethodHandler#invoke
public Object invoke(Object[] argv) throws Throwable {
// 拷贝配置 解析参数
RequestTemplate template = buildTemplateFromArgs.create(argv);
//同URI参数 包含超时时间的Options参数也可在 方法中动态传入
Options options = findOptions(argv);
//默认不开启重启
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
//feign.Retryer.Default的实现机制是尝试5次,之后就抛异常了
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
throw...
}
...
continue;
}
}
}
ReflectiveFeign.BuildTemplateByResolvingArgs#create
public RequestTemplate create(Object[] argv) {
//拷贝
RequestTemplate mutable = RequestTemplate.from(metadata.template());
//HardCodedTarget
mutable.feignTarget(target);
if (metadata.urlIndex() != null) {
int urlIndex = metadata.urlIndex();
//如果方法中有uri参数(只能是http://服务名/xx/xx形式,可以只有http://服务名),
//动态拼接方法上的requestmappin 路径值
mutable.target(String.valueOf(argv[urlIndex]));
}
Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
//key是下标 value是参数名称
int i = entry.getKey();
Object value = argv[entry.getKey()];
if (value != null) { // Null values are skipped.
if (indexToExpander.containsKey(i)) {
//基于ConversionService参数类型转换
value = expandElements(indexToExpander.get(i), value);
}
for (String name : entry.getValue()) {
// 接口需要的真正 key value
varBuilder.put(name, value);
}
}
}
//body PathVariable header 处理
RequestTemplate template = resolve(argv, mutable, varBuilder);
//queryMap headerMap处理 queryMap就是RequestParam声明在map上,headerMap同理,
//这样做传参更灵活,但光看方法不能知道到底传递了什么参数,需要到调用方法处查看
return template;
}
SynchronousMethodHandler#executeAndDecode
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
//调用RequestInterceptor拦截器
Request request = targetRequest(template);
Response response;
long start = System.nanoTime();
try {
//基于服务名本地查找本地缓存的服务列表 默认使用RoundRobinLoadBalance挑选服务实例,获取真正的ip地址
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 12
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
//io异常 就抛出RetryableException 让外层有重试的机会
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
if (decoder != null)
return decoder.decode(response, metadata.returnType());
CompletableFuture<Object> resultFuture = new CompletableFuture<>();
//内部会基于http响应码判断是否调用 feign.codec.ErrorDecoder#decode 处理异常,、
//如果响应头包含Retry-After ,内部还会再抛出RetryableException
asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
metadata.returnType(),
elapsedTime);
....
}
ErrorDecoder.Default#decode
public Exception decode(String methodKey, Response response) {
FeignException exception = errorStatus(methodKey, response);
//有无Retry-After响应头
Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER));
if (retryAfter != null) {
return new RetryableException(
response.status(),
exception.getMessage(),
response.request().httpMethod(),
exception,
retryAfter,
response.request());
}
return exception;
}
FeignException#errorStatus
private static FeignException errorStatus(int status,
String message,
Request request,
byte[] body) {
//根据响应码 抛出各类子异常
// status >= 400 && status < 500
if (isClientError(status)) {
return clientErrorStatus(status, message, request, body);
}
//status >= 500 && status <= 599;
if (isServerError(status)) {
return serverErrorStatus(status, message, request, body);
}
return new FeignException(status, message, request, body);
}
基于上面我们可以知道,FeignClient的重试机制就是判断调用的时候是否发生了IO异常或者异常了但返回Retry-After响应头,所以像404,502,503等常见错误是不会进行重试的,那么我们只需要重写ErrorDecoder#decode 方法,只要响应码在404,502,503抛出RetryableException就可以了
//实例化就变成全局的了,
//Retry同理,如果实例化一个Retryer类,则该类变为全局默认配置。建议Retry只用来查询数据,会对服务方产生数据变化的
//接口不建议使用。有可能对方处理超时了,而调用方触发了重试,导致产生多个数据
@Component
public class FeignErrorDecoder extends ErrorDecoder.Default {
@Override
public Exception decode(String methodKey, Response response) {
if (response.status() == 502 || response.status() == 503 || response.status() == 404) {
return new RetryableException(response.status(), String.format("%s retried 5 times",response.request().url()),
response.request().httpMethod(), null, response.request());
}
return super.decode(methodKey,response);
}
}
4.总结
@EnableEurekaClients注解为我们import了FeignClientsRegistrar,该类在容器扫描注册bean时,会扫描classpath下配置的包路径,找到带@FeignClient的接口类,注册beanDefinition,而definition的实例化是由FeignClientFactoryBean辅助完成的。FeignClientFactoryBean.getTarget方法读取容器中包含超时时间,encode,decode等配置信息,解析方法元信息,创建与之对应的MethodHandler,用来注入参数和调用请求。将方法与MethodHandler的映射关系收集起来,注册一个代理对象,我们声明@FeignClient的接口类,其调用逻辑就是委托给代理对象,继而代理对象通过映射关系找到 MethodHandler 继续委托处理的。
Feign的源码中留下了很多可供扩展的地方,在服务级别的参数,如Retryer, ErrorDecoder可通过配置文件,注解引入配置类, 或者直接实例化 不同的FeignBuilderCustomizer来根据条件定制配置。 接口级别的参数可以通过传参URI、Request.Options来动态调整请求地址,超时时间等。 同时可以通过自定义 RequestInterceptor的方式拦截请求,添加自定义逻辑。