一:@FeignClient 注解分析:
首先来分析一下@FeignClient这个注解,点进去这个注解看下,代码如下:
/**
* Annotation for interfaces declaring that a REST client with that interface should be
* created (e.g. for autowiring into another component). If ribbon is available it will be
* used to load balance the backend requests, and the load balancer can be configured
* using a <code>@RibbonClient</code> with the same name (i.e. value) as the feign client.
*
* @author Spencer Gibb
* @author Venil Noronha
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
/**
* The name of the service with optional protocol prefix. Synonym for {@link #name()
* name}. A name must be specified for all clients, whether or not a url is provided.
* Can be specified as property key, eg: ${propertyKey}.
*/
@AliasFor("name")
String value() default "";
/**
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
*
* @deprecated use {@link #name() name} instead
*/
@Deprecated
String serviceId() default "";
/**
* This will be used as the bean name instead of name if present, but will not be used as a service id.
*/
String contextId() default "";
/**
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
*/
@AliasFor("value")
String name() default "";
/**
* Sets the <code>@Qualifier</code> value for the feign client.
*/
String qualifier() default "";
/**
* An absolute URL or resolvable hostname (the protocol is optional).
*/
String url() default "";
/**
* Whether 404s should be decoded instead of throwing FeignExceptions
*/
boolean decode404() default false;
/**
* A custom <code>@Configuration</code> for the feign client. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
*/
Class<?>[] configuration() default {};
/**
* Fallback class for the specified Feign client interface. The fallback class must
* implement the interface annotated by this annotation and be a valid spring bean.
*/
Class<?> fallback() default void.class;
/**
* Define a fallback factory for the specified Feign client interface. The fallback
* factory must produce instances of fallback classes that implement the interface
* annotated by {@link FeignClient}. The fallback factory must be a valid spring
* bean.
*
* @see feign.hystrix.FallbackFactory for details.
*/
Class<?> fallbackFactory() default void.class;
/**
* Path prefix to be used by all method-level mappings. Can be used with or without
* <code>@RibbonClient</code>.
*/
String path() default "";
/**
* Whether to mark the feign proxy as a primary bean. Defaults to true.
*/
boolean primary() default true;
}
@Target(ElementType.TYPE),表示FeignClient注解作用目标在接口上。
@Retention(RetentionPolicy.RUNTIME),表示该注解会在Class字节码文件中存在,在运行时可以通过反射获取到。
@Documented,表示该注解将被包含在Javadoc中
简单的介绍下各个属性。
value(), name()都是一样的,都是定义的serviceId。
uri()直接写硬编码的地址,一般用来调试用。
decode404()针对返回结果404被解码或者抛异常。
configuration()为feign的配置类,默认的配置类FeignClientsConfiguration
fallback()为相关的熔断类。
二:默认配置类FeignClientsConfiguration源码分析
接下来看下FeignClientsConfiguration。需要注意一下带有@Bean 和 @ConditionalOnMissingBean这两个注解的方法。代码如下:
package org.springframework.cloud.openfeign;
/**
* @author Dave Syer
* @author Venil Noronha
*/
@Configuration
public class FeignClientsConfiguration {
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
@Autowired(required = false)
private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();
@Autowired(required = false)
private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();
@Autowired(required = false)
private Logger logger;
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
@Bean
@ConditionalOnMissingBean
public Encoder feignEncoder() {
return new SpringEncoder(this.messageConverters);
}
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
@Bean
public FormattingConversionService feignConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService();
for (FeignFormatterRegistrar feignFormatterRegistrar : feignFormatterRegistrars) {
feignFormatterRegistrar.registerFormatters(conversionService);
}
return conversionService;
}
@Configuration
@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();
}
}
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
@Bean
@ConditionalOnMissingBean(FeignLoggerFactory.class)
public FeignLoggerFactory feignLoggerFactory() {
return new DefaultFeignLoggerFactory(logger);
}
}
可以知道这个配置类注入了很多个bean,@ConditionalOnMissingBean注解表示 如果没有注入该类的Bean,那么就会默认注入一个Bean。上一篇文章中曾写过的配置类 FeignConfig,代码如下:
package com.example.eurekafeignclient.config;
//注入Retryer类的实例,这样在远程调用失败后,feign会进行重试
@Configuration
public class FeignConfig {
public Retryer feignRetryer(){
//Feign 默认的配置在请求失败后,重试次数为0,即不重试。
//重试间隔 为100毫秒,最大重试时间为1秒,重试次数为5次
//return new Retryer.Default();
return new Retryer.Default(100,SECONDS.toMillis(1),5);
}
}
重新实例化一个Retryer的Bean去覆盖掉默认的Retryer bean,表明FeignClientsConfiguration支持自定义配置。我们只需要重写FeignClientsConfiguration类中的Bean去覆盖默认的配置Bean,就可以达到自定义配置的目的。
二:工作原理源码分析
接着我们来看下在启动类上的@EnableFeignClients 注解,代码如下:
package org.springframework.cloud.openfeign;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
* declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
* {@code @ComponentScan(basePackages="org.my.pkg")}.
* @return the array of 'basePackages'.
*/
String[] value() default {};
/**
* Base packages to scan for annotated components.
* <p>
* {@link #value()} is an alias for (and mutually exclusive with) this attribute.
* <p>
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
* package names.
*
* @return the array of 'basePackages'.
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
*
* @return the array of 'basePackageClasses'.
*/
Class<?>[] basePackageClasses() default {};
/**
* A custom <code>@Configuration</code> for all feign clients. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
*/
Class<?>[] defaultConfiguration() default {};
/**
* List of classes annotated with @FeignClient. If not empty, disables classpath scanning.
* @return list of FeignClient classes
*/
Class<?>[] clients() default {};
}
@Import(FeignClientsRegistrar.class),注入 FeignClientsRegistrar 的Bean。
跟进这个注解,代码如下:
public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, BeanClassLoaderAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
private final Logger logger = LoggerFactory.getLogger(FeignClientsRegistrar.class);
private ResourceLoader resourceLoader;
private ClassLoader classLoader;
public FeignClientsRegistrar() {
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//1、扫描是否有EnableFeignClients注解,然后加载配置( 扫描EnableFeignClients标签里配置的信息,注册到beanDefinitionNames中。)
registerDefaultConfiguration(metadata, registry);
//2、扫描所有带有@FeignClient注解的类 ,然后注册
registerFeignClients(metadata, registry);
}
//...
}
从上面代码可以发现主要做了 扫描 是否有EnableFeignClients注解并注册 到Spring容器中和扫描
扫描所有带有@FeignClient注解的类并注册到Spring容器中两件事情。
2.1 扫描是否有EnableFeignClients注解,并注册到Spring容器中
跟进registerDefaultConfiguration(metadata, registry); 这个方法中,
//扫描是否有EnableFeignClients注解,然后加载配置
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//读取启动类上面@EnableFeignClients注解中声明feign相关配置类,
//默认name为default,一般情况下无需配置。用默认的FeignAutoConfiguration即可。
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();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
点进去registerClientConfiguration();这个方法中
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());
}
上面将bean配置类包装成FeignClientSpecification,注入到容器。该对象非常重要,包含FeignClient需要的重试策略,超时策略,日志等配置,如果某个服务没有设置,则读取默认的配置。
至此,扫描是否有EnableFeignClients注解,并注册到Spring容器中
2.2 扫描所有带有@FeignClient注解的类,将服务接口注册到Spring容器中
点进去registerFeignClients(metadata, registry); 这个方法
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
//省略代码...根据basePackages找到包下所有FeignClient注解的类,
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
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"));
//重点分析这个
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
可以看到上面又调用了registerClientConfiguration注册配置的方法,这里是第二处调用。这里主要是将扫描的目录下,每个项目的配置类加载的容器当中。
注册到容器中,什么时候会用到呢?具体又如何使用呢?别着急,后面会有介绍。
我们先会回到继续主流程,继续看注册feignClient的方法,跟进registerFeignClient(registry, annotationMetadata, attributes);
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//声明代理类名称
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 = name + "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 });
//将使用了@FeignClient注解的接口全部注入到Spring容器中。
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
跟进 BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
至此,扫描所有带有@FeignClient注解的类,将服务接口注册到Spring容器中。
三:Feign是如何进行服务调用的?
通过jdk的代理,当请求Feign Client的方法时会被拦截,代码在ReflectiveFeign类,代码如下:
/**
* creates an api binding to the {@code target}. As this invokes reflection, care should be taken
* to cache the result.
*/
@SuppressWarnings("unchecked")
@Override
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>();
//将要被代理的方法全部在methodToHandler中;
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);
//使用jdk的动态代理为指定的Feign接口生成代理对象
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
在SynchronousMethodHandler类进行拦截处理,当被FeignClient的方法被拦截会根据参数生成RequestTemplate对象,该对象就是http请求的模板,代码如下:
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) {
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;
}
}
}
其中有个executeAndDecode()方法,该方法是通RequestTemplate生成Request请求对象,然后用Http Client 获取response,即通过 Http Client 进行 Http 请求来获取响应。代码如下:
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = targetRequest(template);
...//省略代码
response = client.execute(request, options);
...//省略代码
}
四:Feign是如何实现负载均衡的?
由FeignRibbonClientAutoConfiguration自动配置类可知,向容器注入的是LoadBalancerFeignClient(这个类与Ribbon研究中的LoadBalancerClient类在功能上有相似性),即负载均衡客户端。LoadBalancerFeignClient 类代码如下:
package org.springframework.cloud.openfeign.ribbon;
/**
* @author Dave Syer
*
*/
public class LoadBalancerFeignClient implements Client {
static final Request.Options DEFAULT_OPTIONS = new Request.Options();
private final Client delegate;
private CachingSpringLoadBalancerFactory lbClientFactory;
private SpringClientFactory clientFactory;
public LoadBalancerFeignClient(Client delegate,
CachingSpringLoadBalancerFactory lbClientFactory,
SpringClientFactory clientFactory) {
this.delegate = delegate;
this.lbClientFactory = lbClientFactory;
this.clientFactory = clientFactory;
}
@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) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
IClientConfig getClientConfig(Request.Options options, String clientName) {
IClientConfig requestConfig;
if (options == DEFAULT_OPTIONS) {
requestConfig = this.clientFactory.getClientConfig(clientName);
} else {
requestConfig = new FeignOptionsClientConfig(options);
}
return requestConfig;
}
protected IOException findIOException(Throwable t) {
if (t == null) {
return null;
}
if (t instanceof IOException) {
return (IOException) t;
}
return findIOException(t.getCause());
}
public Client getDelegate() {
return this.delegate;
}
static URI cleanUrl(String originalUrl, String host) {
String newUrl = originalUrl;
if(originalUrl.startsWith("https://")) {
newUrl = originalUrl.substring(0, 8) + originalUrl.substring(8 + host.length());
} else if(originalUrl.startsWith("http")) {
newUrl = originalUrl.substring(0, 7) + originalUrl.substring(7 + host.length());
}
StringBuffer buffer = new StringBuffer(newUrl);
if((newUrl.startsWith("https://") && newUrl.length() == 8) ||
(newUrl.startsWith("http://") && newUrl.length() == 7)) {
buffer.append("/");
}
return URI.create(buffer.toString());
}
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
static class FeignOptionsClientConfig extends DefaultClientConfigImpl {
public FeignOptionsClientConfig(Request.Options options) {
setProperty(CommonClientConfigKey.ConnectTimeout,
options.connectTimeoutMillis());
setProperty(CommonClientConfigKey.ReadTimeout, options.readTimeoutMillis());
}
@Override
public void loadProperties(String clientName) {
}
@Override
public void loadDefaultValues() {
}
}
}
具体看execute 方法,代码如下:
@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);
// 封装 Ribbon 请求信息
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
// 获取 Ribbon 请求相关配置信息
IClientConfig requestConfig = getClientConfig(options, clientName);
// 执行操作(根据封装的 RibbonRequest (ribbon请求) 和 IClientConfig (ribbon请求配置属性) 发起请求。)
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
根据 RibbonRequest 和 IClientConfig 发起请求。
LoadBalancerFeignClient#execute return 执行的代码如下:
lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
该代码中,包括两个执行逻辑**(重点)**
**//得到 FeignLoadBalancer 客户端负载均衡实例对象
LoadBalancerFeignClient # lbClient(clientName)
// 实现负载均衡
FeignLoadBalancer # executeWithLoadBalancer**
我们分别来看看这两个执行逻辑。
1.聚焦LoadBalancerFeignClient#lbClient
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
LoadBalancerFeignClient # lbClient 逻辑:通过 clientName 获取 FeignLoadBalancer 对象。
而 FeignLoadBalancer 对象的获取是从 CachingSpringLoadBalancerFactory#create 中获取的。
跟进 create 这个方法:
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 = loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client);
return client;
}
CachingSpringLoadBalancerFactory 是一个缓存工厂,缓存了 clientName 对应 FeignLoadBalancer 关系结构。
通过代码,我们可以知道 FeignLoadBalancer 是实现 Feign 客户端负载均衡调用的一个类, FeignLoadBalancer 对 Ribbon ILoadBalancer 进行了封装,将 RIbbon 的数据 如:ILoadBalancer, IClientConfig 直接以成员变量的形式存入 FeignLoadBalancer 中。
所有 lbClient(clientName) 得到 FeignLoadBalancer 客户端负载均衡实例对象之后,就是开始调用 FeignLoadBalancer#executeWithLoadBalancer。
2.聚焦 FeignLoadBalancer # executeWithLoadBalancer
点进去该方法:
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) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
该方法有两个主要逻辑:
LoadBalancerCommand#submit
该方法实现客户端负载均衡,根据 IRule(官方默认实现: ZoneAvoidanceRule) 从 ILoadBalancer(Eureka服务实例管理,默认:DynamicServerListLoadBalancer) 中获取一个Server实例对象。
this.execute(requestForServer, requestConfig)
根据 LoadBalancerCommand#submit 获取到的 Server ,调用 FeignLoadBalancer#execute 发起服务调用请求,并返回结果。
点击command.submit()的submit()方法,到达LoadBalancerCommand 类
submit()部分代码如下:
// 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) {
context.incAttemptCount();
loadBalancerContext.noteOpenConnection(stats);
上述代码中有个selectServe(),该方法是选择服务的进行负载均衡的方法
/**
* Return an Observable that either emits only the single requested server
* or queries the load balancer for the next server on each subscription
*/
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) {
next.onError(e);
}
}
});
}
3.负载均衡总结
1、LoadBalancerFeignClient#execute() 是整个 Feign Client 的执行入口
2、LoadBalancerFeignClient#execute() 方法主线逻辑:
封装 RibbonReuqest 请求对象,从容器中获取 Ribbon 请求配置参数信息 IConfigClient,根据 RibbonRequest 和 IConfigClient 获取FeignLoadBalancer 负载均衡实例对象。
3、FeignLoadBalancer 根据 Ribbon 的 IRule 规则从 Iloadbalancer 获取一个服务实例 server。
4、FeignLoadBalancer#execute 根据获取到的服务实例 Server 发起请求调用。然后返回。
5.FeignLoadBalancer 其实就是 Feign 对 Ribbon 的一层封装,实际调用的就是 Ribbon 的负载均衡逻辑,包括 IRule 等 进行服务实例的获取。
所以说,Feign 最后还是走 Riboon的
五:总结
总到来说,Feign的源码实现的过程如下:
1.首先通过@EnableFeignCleints注解开启FeignCleint,根据Feign的规则实现接口,并加@FeignCleint注解
2.程序启动后,会进行包扫描,扫描所有的@ FeignCleint的注解的类,并将这些信息注入到ioc容器中。
3.当接口的方法被调用,通过jdk的代理,来生成具体的RequesTemplate,
4.跟进RequesTemplate再生成Http请求的Request对象。
5.Request 对象交给 Feign Client 去处理。
5.最后Feign Client被封装到LoadBalancerFeignClient类,这个类是 Feign 对 Ribbon 的一层封装,做到了负载均衡。
参考资料:
https://blog.csdn.net/qq_23202687/article/details/94408008
https://cloud.tencent.com/developer/article/1009212