类结构
主要类
- FeignAutoConfiguration:创建NamedContextFactory。
- FeignContext:NamedContextFactory的实现类,该类中含有FeignClientsConfiguration和FeignClientSpecification属性;FeignClientsConfiguration在创建FeignContext时默认加载到FeignContext中,如下图一所示;FeignClientSpecification包含服务名和对应Feign配置类,如下图二所示。
- HttpClientFeignLoadBalancedConfiguration:负载均衡类。
图一
图二
流程
通过启动类中增加@EnableFeignClients注解引入Feign
在EnableFeignClients中通过导入FeignClientsRegistrar创建Feign代理
FeignClientsRegistrar 继承自ImportBeanDefinitionRegistrar,在Spring启动时会调用registerBeanDefinitions方法,在registerBeanDefinitions方法中会向Spring容器中注入FeignClientSpecification配置类和创建FeignClientFactoryBean的类
注:所谓配置信息指的是@FeignClient(value = “tmp-test1”,configuration = )注解中configuration,该configuration引用的类和服务名生成FeignClientSpecification类,最终会放到FeignContext类(具体请看FeignAutoConfiguration),FeignContext在创建时会包含FeignClientsConfiguration和FeignClientSpecification属性,FeignClientsConfiguration和FeignClientSpecification类是用来创建feign.Decoder, feign.Encoder和a feign.Contract,FeignClientsConfiguration为全局配置,为每个Feign客户端创建一个新feign.Encoder和a feign.Contract,而后者FeignClientSpecification是针对特定Feign客户端特定配置,FeignClientSpecification将覆盖FeignClientsConfiguration属性;也可以在配置文件中配置,它们的优先级 【 配置文件>FeignClientSpecification>FeignClientsConfiguration】
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 加载Feign默认配置信息
this.registerDefaultConfiguration(metadata, registry);
this.registerFeignClients(metadata, registry);
}
}
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
} else {
name = "default." + metadata.getClassName();
}
this.registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
}
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
scanner.setResourceLoader(this.resourceLoader);
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients"));
Object basePackages;
if (clients != null && clients.length != 0) {
final Set<String> clientClasses = new HashSet();
basePackages = new HashSet();
Class[] var9 = clients;
int var10 = clients.length;
for(int var11 = 0; var11 < var10; ++var11) {
Class<?> clazz = var9[var11];
((Set)basePackages).add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(new FeignClientsRegistrar.AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
} else {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = this.getBasePackages(metadata);
}
Iterator var17 = ((Set)basePackages).iterator();
while(var17.hasNext()) {
String basePackage = (String)var17.next();
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
Iterator var21 = candidateComponents.iterator();
while(var21.hasNext()) {
BeanDefinition candidateComponent = (BeanDefinition)var21.next();
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 = this.getClientName(attributes);
// 获取配置中的configuration,设置configuration属性
this.registerClientConfiguration(registry, name, attributes.get("configuration"));
// 在Spring容器中注入FeignClientFactoryBean类,在启动时创建FeignClientFactoryBean对象
this.registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
// 获取配置中的configuration,创建FeignClientSpecification对象,设置configuration属性
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());
}
}
在registerFeignClient方法中,在向Spring容器中注入FeignClientFactoryBean时会设置相应属性。
属性解释如下
- url和path,path的作用是拼接到url后边,例如:url->http://localhost:8080, path->sys,则feign请求会去调用http://localhost:8080/sys
- name,主要是针对有注册中心,表示服务名,获取顺序为注解属性serviceId->name->value
- contextId,feign会为每一个contextId创建一个ApplicationContext,里面包含对应的Feign属性,获取顺序为注解属性contextId->serviceId->name->value
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
this.validate(attributes);
definition.addPropertyValue("url", this.getUrl(attributes));
definition.addPropertyValue("path", this.getPath(attributes));
String name = this.getName(attributes);
definition.addPropertyValue("name", name);
String contextId = this.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(2);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean)attributes.get("primary");
beanDefinition.setPrimary(primary);
String qualifier = this.getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
}
FeignClientFactoryBean 是Feign接口的代理类,继承自FactoryBean,所以当获取Bean时会调用getObject方法,
- 接着会调用getTarget方法,
- 从Spring上下中获取FeignContext对象
- 创建feign.Budilder ,并且配置feign.Budilder 属性
配置文件中自定义配置具体请看FeignClientProperties.FeignClientConfiguration类信息
public static class FeignClientConfiguration {
private Level loggerLevel;// 是否打印日志
private Integer connectTimeout;// 连接时间
private Integer readTimeout;// 读取超时时间
private Class<Retryer> retryer;// 重试次数
private Class<ErrorDecoder> errorDecoder;// 响应失败解析
private List<Class<RequestInterceptor>> requestInterceptors;// 请求拦截器
private Boolean decode404;// 404解析器
private Class<Decoder> decoder;// 响应成功解析器
private Class<Encoder> encoder;
private Class<Contract> contract;
}
注:(FeignContext是在FeignAutoConfiguration类中自动加载的,同时该类包含了所有的FeignClientSpecification对象)
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
public Object getObject() throws Exception {
return this.getTarget();
}
<T> T getTarget() {
// 从Spring上下中获取FeignContext对象
FeignContext context = (FeignContext)this.applicationContext.getBean(FeignContext.class);
// 获取Feign,配置feign属性,通过查找所有,没有则用默认的feign属性
Builder builder = this.feign(context);
if (!StringUtils.hasText(this.url)) {// url为空,则是利用注册中心,否则,通过直连http调用
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
} else {
this.url = this.name;
}
this.url = this.url + this.cleanPath();
return this.loadBalance(builder, context, new HardCodedTarget(this.type, this.name, this.url));
} else {
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + this.cleanPath();
Client client = (Client)this.getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = (Targeter)this.get(context, Targeter.class);
return targeter.target(this, builder, context, new HardCodedTarget(this.type, this.name, url));
}
}
protected Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = (FeignLoggerFactory)this.get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
Builder builder = ((Builder)this.get(context, Builder.class)).logger(logger).encoder((Encoder)this.get(context, Encoder.class)).decoder((Decoder)this.get(context, Decoder.class)).contract((Contract)this.get(context, Contract.class));
this.configureFeign(context, builder);
return builder;
}
}
通过Targeter创建Feign代理类
protected <T> T loadBalance(Builder builder, FeignContext context, HardCodedTarget<T> target) {
Client client = (Client)this.getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = (Targeter)this.get(context, Targeter.class);
return targeter.target(this, builder, context, target);
} else {
throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
}
如果Builder 是feign.hystrix.HystrixFeign.Builder,则获取Hystrix降级接口,创建HystrixInvocationHandler代理类,否则创建非Hystrix代理类, 最终创建SynchronousMethodHandler代理类执行具体操作
class HystrixTargeter implements Targeter {
HystrixTargeter() {
}
public <T> T target(FeignClientFactoryBean factory, Builder feign, FeignContext context, HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
} else {
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder)feign;
SetterFactory setterFactory = (SetterFactory)this.getOptional(factory.getName(), context, SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
Class<?> fallback = factory.getFallback();
if (fallback != Void.TYPE) {
// 如果又返回值,获取降级接口,创建回调代理类
return this.targetWithFallback(factory.getName(), context, target, builder, fallback);
} else {
Class<?> fallbackFactory = factory.getFallbackFactory();
return fallbackFactory != Void.TYPE ? this.targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory) : feign.target(target);
}
}
private <T> T targetWithFallback(String feignClientName, FeignContext context, HardCodedTarget<T> target, feign.hystrix.HystrixFeign.Builder builder, Class<?> fallback) {
T fallbackInstance = this.getFromContext("fallback", feignClientName, context, fallback, target.type());
return builder.target(target, fallbackInstance);
}
}
SynchronousMethodHandler
- 获取请求参数,如连接读取超时时间
- 调用拦截器RequestInterceptor
- 请求
- 返回异常,抛出的异常信息,通过ErrorDecoder处理,否则请求正确,解析响应,默认走SpringDecoder,SpringDecorder在Spring启动的时候会注入参数解析器,用来解析参数
final class SynchronousMethodHandler implements MethodHandler {
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = this.buildTemplateFromArgs.create(argv);
// 获取请求参数,如连接读取超时时间
Options options = this.findOptions(argv);
Retryer retryer = this.retryer.clone();
while(true) {
try {
return this.executeAndDecode(template, options);
} catch (RetryableException var9) {
RetryableException e = var9;
try {
retryer.continueOrPropagate(e);
} catch (RetryableException var8) {
Throwable cause = var8.getCause();
if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
throw cause;
}
throw var8;
}
if (this.logLevel != Level.NONE) {
this.logger.logRetry(this.metadata.configKey(), this.logLevel);
}
}
}
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
// 调用拦截器RequestInterceptor
Request request = this.targetRequest(template);
if (this.logLevel != Level.NONE) {
this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
}
long start = System.nanoTime();
Response response;
try {
// 发送请求
response = this.client.execute(request, options);
} catch (IOException var16) {
if (this.logLevel != Level.NONE) {
this.logger.logIOException(this.metadata.configKey(), this.logLevel, var16, this.elapsedTime(start));
}
throw FeignException.errorExecuting(request, var16);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
boolean shouldClose = true;
Object var11;
try {
if (this.logLevel != Level.NONE) {
response = this.logger.logAndRebufferResponse(this.metadata.configKey(), this.logLevel, response, elapsedTime);
}
if (Response.class == this.metadata.returnType()) {
Response var19;
if (response.body() == null) {
var19 = response;
return var19;
}
if (response.body().length() != null && (long)response.body().length() <= 8192L) {
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
Response var21 = response.toBuilder().body(bodyData).build();
return var21;
}
shouldClose = false;
var19 = response;
return var19;
}
Object result;
if (response.status() >= 200 && response.status() < 300) {
if (Void.TYPE == this.metadata.returnType()) {
result = null;
return result;
}
result = this.decode(response);
shouldClose = this.closeAfterDecode;
var11 = result;
return var11;
}
if (!this.decode404 || response.status() != 404 || Void.TYPE == this.metadata.returnType()) {
// 返回异常,抛出的异常信息
throw this.errorDecoder.decode(this.metadata.configKey(), response);
}
// 请求正确,解析响应,默认走SpringDecoder
result = this.decode(response);
shouldClose = this.closeAfterDecode;
var11 = result;
} catch (IOException var17) {
if (this.logLevel != Level.NONE) {
this.logger.logIOException(this.metadata.configKey(), this.logLevel, var17, elapsedTime);
}
throw FeignException.errorReading(request, response, var17);
} finally {
if (shouldClose) {
Util.ensureClosed(response.body());
}
}
return var11;
}
}
Feign 如何设置超时时间(connectionTimeout、readTimout)
对于这个问题网上有很多相关资料,大体上有两种方案,一种是通过设置 ribbon 的超时时间(因为 Feign 是基于 ribbon 来实现的,所以通过 ribbon 的超时时间设置也能达到目的),一种是直接设置 Feign 的超时时间,我将会在下边的篇幅里分别说一下如何通过application.yml 配置文件来设置超时时间。(注:这里是以 Feign 的默认客户端(Client.Default)来说的!!!!)
1、Ribbon
对于 ribbon 又分为全局配置和指定服务配置:
1.1 全局配置
对所有的服务该配置都生效
ribbon:
ReadTimeout: 30000 #单位毫秒
ConnectTimeout: 30000 #单位毫秒
1.2 指定服务配置
下边代码中的 annoroad-beta 是服务的名称,意思是该配置只针对名为 annoroad-beta 的服务有效,根据实际的需要替换成你自己的服务名
tmp:
ribbon:
ReadTimeout: 30000 #单位毫秒
ConnectTimeout: 30000 #单位毫秒
2、Feign
与 Ribbon 一样,Feign 也分为全局配置和指定服务配置:
2.1 全局配置
下边代码中使用的 feign.client.config.default ,意思是所有服务都采用该配置
feign:
client:
config:
default:
connectTimeout: 10000 #单位毫秒
readTimeout: 10000 #单位毫秒
2.2 指定服务配置
下边代码中使用的 feign.client.config.annoroad-beta,意思是该配置只针对名为 annoroad-beta 的服务有效,可以根据实际的需要替换成你自己的服务名
`
feign:
client:
config:
tmp:
connectTimeout: 10000 #单位毫秒
readTimeout: 10000 #单位毫秒
总结
如果同时配置了Ribbon、Feign,那么 Feign 的配置将生效
Ribbon 的配置要想生效必须满足微服务相互调用的时候通过注册中心,如果你是在本地通过 @FeignClient 注解的 url 参数进行服务相互调用的测试,此时 ribbon 设置的超时时间将会失效,但是通过 Feign 设置的超时时间不会受到影响(仍然会生效)
综上所述建议使用 Feign 的来设置超时时间
具体请看LoadBalancerFeignClient类
public class FeignLoadBalancer extends AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {
public FeignLoadBalancer.RibbonResponse execute(FeignLoadBalancer.RibbonRequest request, IClientConfig configOverride) throws IOException {
Options options;
if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Options(override.connectTimeout(this.connectTimeout), override.readTimeout(this.readTimeout));
} else {
options = new Options(this.connectTimeout, this.readTimeout);
}
// 调用request中的Client的execute类,其中options封装了Feign配置信息,在Client中会调用convertAndSend进行转换
Response response = request.client().execute(request.toRequest(), options);
return new FeignLoadBalancer.RibbonResponse(request.getUri(), response);
}
}
public interface Client {
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = this.convertAndSend(request, options);
return this.convertResponse(connection, request);
}
}
熔断
开启Hystrix
feign:
hystrix:
enabled: true
@FeignClient(value = "tmp-test1",fallback = HelloServicFallback.class)
public interface HelloFeign {
@GetMapping("/feign")
public String feign();
}
实现HelloFeign
@Component
public class HelloServicFallback implements HelloFeign{
public String feign() {
return "error";
}
}
或打印错误日志需要实现FallbackFactory接口
@Component
@Slf4j
public class HelloServicFallbackFactory implements FallbackFactory<IUserClient> {
@Override
public HelloFeign create(Throwable throwable) {
return new HelloFeign() {
@Override
public String feign() {
log.error("错误", throwable.getMessage());
return "error";
}
};
}
}