Feign是SpringCloud组件中一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。
OpenFeign 是SpringCloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等。OpenFeign 的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
核心类包括:
FeignAutoConfiguration
、FeignRibbonClientAutoConfiguration
。
注解解释:
@EnableFeignClients
Feign解析流程:
作为 ImportBeanDefinitionRegistrar 类型的子类,其准备过程参考文章
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,ResourceLoaderAware, EnvironmentAware {
//metadata 启动类相关注解元信息
// registry:容器DefaultListableBeanFactory
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);//解析启动类
registerFeignClients(metadata, registry);// 解析启动类存在的注解EnableFeignClients
}
private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry){
String name = EnableFeignClients.class.getName();
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(name, true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}else {
name = "default." + metadata.getClassName();// name = default.net.csdn.FeignApp
}
registerClientConfiguration(registry, name,defaultAttrs.get("defaultConfiguration"));
}
}
public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
// 从注解EnableFeignClients 获取 clients 属性。跟注解@FeignClient标明的Feign客户端一样
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
// 表明@EnableFeignClients中属性clients为空,即Feign客户端是通过@FeignClient注解标注的。-- 通常使用方法
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}else {// @EnableFeignClients 注解中利用属性 clients 获取FeignClient客户端。
...
}
// 扫描 @FeignClient注解标明的Feign客户端 所在的包路径。为啥扫描?
// 需要将所有 Feign客户端 即接口通过FactoryBean接口功能完成代理
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata am = beanDefinition.getMetadata();
String canonicalName = FeignClient.class.getCanonicalName();
// 获取到 注解FeignClient 全部的属性
Map<String, Object> attributes = am.getAnnotationAttributes(canonicalName);
String name = getClientName(attributes);//获取 @FeignClient注解 中name属性
//name + "." + FeignClientSpecification:provider-service.FeignClientSpecification
// 注册BeanDefinition,其中beanName为provider-service.FeignClientSpecification,其中
//BeanDefinition中class属性为FeignClientSpecification
registerClientConfiguration(registry, name,attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
// attributes:注解@FeignClient的属性集合
private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata am, Map<String,
Object> ab) {
// className:net.csdn.service.FeignRemoteService
String className = am.getClassName();
BeanDefinitionBuilder definition =
BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
definition.addPropertyValue("url", getUrl(ab));
definition.addPropertyValue("path", getPath(ab));
String name = getName(ab);
definition.addPropertyValue("name", name);
String contextId = getContextId(ab);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", ab.get("decode404"));
definition.addPropertyValue("fallback", ab.get("fallback"));
definition.addPropertyValue("fallbackFactory", ab.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient";
AbstractBeanDefinition bd = definition.getBeanDefinition();
...
BeanDefinitionHolder holder = new BeanDefinitionHolder(bd, className,new String[] { alias });
//注册BeanDefinition,其中beanName为Feign客户端对应的全限定名,BeanDefinition中class属性为FeignClientFactoryBean
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
...
private void registerClientConfiguration(BeanDefinitionRegistry r, Object name,Object configuration) {
BeanDefinitionBuilder builder =
BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);// 设置 接口Specification 的name属性
builder.addConstructorArgValue(configuration);// 设置 接口Specification 的configuration属性
// name + "." + FeignClientSpecification.class.getSimpleName():
// provider-service.FeignClientSpecification
// 将FeignClientSpecification的bean 定义信息注册到容器DefaultListableBeanFactory中
String simpleName = FeignClientSpecification.class.getSimpleName();
r.registerBeanDefinition(name + "." + simpleName,builder.getBeanDefinition());
}
}
注册@FeignClient注解的Feign客户端。其中主要包括两部分:FeignClientSpecification & FeignClientFactoryBean。
在IOC容器中注册BeanDefinition时包含两部分:
其实对于FeignClientSpecification,启动类也会注册beanName为default.net.csdn.FeignApp.FeignClientSpecification。BeanDefinition中class属性为FeignClientSpecification。
该注解存在Spring.factories文件中,属于Spring动态注册的范畴。
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
@Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
@Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
}
FeignClientsConfiguration
类赋值给其父类NamedContextFactory。public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
所有Feign客户端共享FeignContext。
具体参考文章之NamedContextFactory。
@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
}
通过开关【feign.hystrix.enabled】控制:HystrixFeign。
如上所示,实例HystrixFeign是非单例的。因为每一个Feign客户端都对应一个子容器,每个客户端都有独立的HystrixFeign,其中HystrixFeign就是由子容器创建而成的。所以在父容器中就存在与Feign客户端等量的HystrixFeign。
该注解存在Spring.factories文件中,属于Spring动态注册的范畴。
LoadBalancerFeignClient
。其功能与Mybatis中MapperFactoryBean类似。
当类组件解析其依赖的属性FeignClient客户端组件时会通过Spring解析FactoryBean过程中调用getTarget。
public class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean {
public <T> T getTarget() {
//通过 FeignAutoConfiguration 创建FeignContext实例
FeignContext context = applicationContext.getBean(FeignContext.class);
// 通过 FeignContext 创建Feign.Builder。
Feign.Builder builder = feign(context);
//如果配置URL则直接调用,否则通过ribbon负载均衡
if (!StringUtils.hasText(this.url)) {
...
HardCodedTarget target = new HardCodedTarget<>(this.type, this.name, this.url);
// 通常选择负载均衡方式
return (T) loadBalance(builder, context, target);
}
...
HardCodedTarget target = new HardCodedTarget<>(this.type, this.name, this.url);
return (T) targeter.target(this, builder, context,target);
}
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
//以下其实就是从当前 Feign客户端的应用上下文获取Encoder、Decoder、Contract对应bean实例
// 这些实例都是 FeignClientsConfiguration配置类中依赖的bean实例。参考 NamedContextFactory 得知,在实例化FeignClientsConfiguration过程中,就会实例化Encoder、Decoder、Contract对应bean实例
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
// builder 是从当前Feign客户端的应用上下文获取到的。核心功能就是将FeignClientProperties配置文件中Feign客户端对应的配置信息添加到 当前Feign客户端的应用上下文中,即builder中。这样实现了不同客户端配置信息最终添加到对应的IOC容器中【builder】所以是相互独立、不存在互相影响。
configureFeign(context, builder);
return 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);
// 设置默认的配置值,即跟Feign客户端没有关系。也可以称之为全局配置
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),builder);
// 设置 Feign客户端 对应的配置 contextId ~ Feign客户端的name属性
configureUsingProperties(properties.getConfig().get(this.contextId),builder);
}
}
}
protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration c,Feign.Builder builder) {
if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
// 设置 Feign 相关的连接超时【默认10s】、读超时【默认60s】。
builder.options(new Request.Options(c.getConnectTimeout(),c.getReadTimeout()));
}
}
protected <T> T get(FeignContext context, Class<T> type) {
// 此处开始创建当前Feign客户端的应用上下文,并利用其应用上下文实例化type对应的bean
// 参考 NamedContextFactory
T instance = context.getInstance(this.contextId, type);
return instance;
}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,HardCodedTarget<T> target) {
// 优先从当前子容器获取Client,如果子容器不存在则从父容器获取。最终Client类型为~LoadBalancerFeignClient
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
// 从当前Feign客户端对应应用上下文获取 HystrixTargeter
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
}
}
FeignAutoConfiguration
创建FeignContext。FeignClientsConfiguration
引入支持服务治理【熔断降级】的HystrixFeign
。class HystrixTargeter implements Targeter {
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,FeignContext context,
Target.HardCodedTarget<T> target) {
// 如果没有显式设置 HystrixFeign 的开关,默认Builder 为 抽象类Feign内部静态类
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);// 忽略Feign降级功能
}
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName(): factory.getContextId();
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {// 优先通过Fallback实现Feign降级功能
return targetWithFallback(name, context, target, builder, fallback);
}
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {// 其次通过FallbackFactory实现Feign降级功能
return targetWithFallbackFactory(name, context, target, builder,fallbackFactory);
}
return feign.target(target);// 忽略Feign降级功能
}
}
熔断降级功能是通过 HystrixFeign 实现的。抽象类Feign没有降级策略。
不管feign是作为抽象类Feign还是HystrixFeign,其最终返回的泛型T均为ReflectiveFeign。
以HystrixFeign为例分析:
public final class HystrixFeign {
public <T> T target(Target<T> target, FallbackFactory<? extends T> fallbackFactory) {
// build:返回ReflectiveFeign
return build(fallbackFactory).newInstance(target);
}
}
public class ReflectiveFeign extends Feign {
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>();
...
InvocationHandler handler = factory.create(target, methodToHandler);
// type:通过截图得知即为@FeignClient注解标识的某个Feign客户端
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
return proxy;
}
}
通过 HystrixTargeter 得知Feign客户端存在降级与否的区别。针对这两种区别在生成代理过程中其InvocationHandler
也存在差异。如果是 HystrixFeign 则 InvocationHandler 选择 HystrixInvocationHandler
子类,否则选择FeignInvocationHandler
子类。
该章节着重分析Feign客户端降级策略失效的情况。
public class ReflectiveFeign extends Feign {
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
return dispatch.get(method).invoke(args);//dispatch如图所示
}
}
}
final class SynchronousMethodHandler implements MethodHandler {
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) {
retryer.continueOrPropagate(e);//重试策略
continue;
}
}
}
public Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template);
Response response = client.execute(request, options);//LoadBalancerFeignClient#execute
long start = System.nanoTime();
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
...
return response
}
public class LoadBalancerFeignClient implements Client {
private CachingSpringLoadBalancerFactory lbClientFactory;
private SpringClientFactory clientFactory;// NamedContextFactory的子类
public Response execute(Request request, Request.Options options) throws IOException {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();// @FeignClient注解之name属性
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
// 加载配置信息:ConnectTimeout、ReadTimeout
IClientConfig requestConfig = getClientConfig(options, clientName);
// 调用FeignLoadBalancer其抽象父类AbstractLoadBalancerAwareClient
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
requestConfig).toResponse();
}
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);// 创建 FeignLoadBalancer
}
IClientConfig getClientConfig(Request.Options options, String clientName) {
IClientConfig requestConfig;
//如果在Feign中没有显式配置read-timeout、connect-timeout,此时Feign提供默认值【读超时为60秒、连接超时为10秒】。
// 但是此时 options == DEFAULT_OPTIONS 条件是成立的
if (options == DEFAULT_OPTIONS) {
// 通过 SpringClientFactory 创建clientName对应的请求配置信息【Ribbon中read-timeout、connect-timeout的默认值为1秒】。
requestConfig = this.clientFactory.getClientConfig(clientName);
}else {//Feign中显式配置read-timeout、connect-timeout值
requestConfig = new FeignOptionsClientConfig(options);
}
return requestConfig;
}
}
注意:SpringClientFactory 是属于Ribbon范畴的NamedContextFactory的子类。最终是通过RibbonClientConfiguration
创建requestConfig为DefaultClientConfigImpl
。具体参考Ribbon相关源码解析。
DefaultClientConfigImpl其ConnectTimeout、ReadTimeout的默认值为1秒。总结为如果在Feign中没有显式配置read-timeout、connect-timeout,则对应变量的取值最终选自Ribbon中的默认值。此时Feign中ConnectTimeout、ReadTimeout的默认值将失效。
对于高版本Resilience4j实现的熔断器,如果选择DefaultTargeter则其ConnectTimeout、ReadTimeout配置完全是由FeignEncoderProperties类控制。
feign.client.config.default.connectTimeout是针对全局配置。feign.client.config.serviceName.connectTimeout是设置具体Feign客户端的配置。
public abstract class AbstractLoadBalancerAwareClient extends LoadBalancerContext{
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
return command.submit(//涉及响应式编程
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {// 通过负载策略获取到目标服务某个IP、端口号信息
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
// FeignLoadBalancer#execute调用目标服务
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer,
requestConfig));
}
})
.toBlocking()
.single();
}
}
public class LoadBalancerCommand<T> {
public Observable<T> submit(final ServerOperation<T> operation) {
...
// 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) {
context.setServer(server);
final ServerStats stats = loadBalancerContext.getServerStats(server);
// Called for each attempt and retry
Observable<T> o = Observable
.just(server)
.concatMap(new Func1<Server, Observable<T>>() {
@Override
public Observable<T> call(final Server server) {
...
return operation.call(server).doOnEach(new Observer<T>() {// 回调执行目标服务
...
});
}
});
return o;
}
});
...
return o.onErrorResumeNext(new Func1<Throwable, Observable<T>>() {
@Override
public Observable<T> call(Throwable e) {
...
return Observable.error(e);
}
});
}
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
// 通过上述RibbonClientConfiguration解析,获取到ribbon相关的配置、以及均衡负载策略。详情参考ribbon源码解读
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}
}
// configOverride:feign相关配置信息
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException {
Request.Options options;
if (configOverride != null) {
// 将Feign相关配置直接赋值给Ribbon,如果feign没有相关配置则读取ribbon相关配置
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(
// connectTimeout、readTimeout均为ribbon设置的时间,默认值均为1秒。
override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout));
}
else {// 使用Feign的设置值或者默认值【读超时为60秒、连接超时为10秒】。
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
//options包含最终的超时时间、链接超时等配置
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
源码大致意思:如果openFeign没有设置对应得超时时间,那么将会采用Ribbon的默认超时时间。
高版本:CircuitBreakerAutoConfiguration。