文章目录

1.如何使用spring cloud feign

  • 导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 开始FeignClient
@SpringBootApplication
@EnableFeignClients
public class AdminWebApp {

public static void main(String[] args) {
SpringApplication.run(AdminWebApp.class,args);
}
}
  • FeignClient接口
@FeignClient(value = "ItemCatService")
public interface ItemCatClient {

}

从官网的例子中,可以看出是通过注解驱动的,所以从注解开始看起。

2.spring cloud feign是如何工作的

Feign涉及了两个注解,一个是​​@EnableFeignClients​​​,用来开启 Feign,另一个是​​@FeignClient​​,用来标记要用 Feign 来拦截的请求接口。

  • @EnableFeignClients注解:
package org.springframework.cloud.openfeign;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
String[] value() default {};

String[] basePackages() default {};

Class<?>[] basePackageClasses() default {};

Class<?>[] defaultConfiguration() default {};

Class<?>[] clients() default {};
}
  • @FeignClient注解:
package org.springframework.cloud.openfeign;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
@AliasFor("name")
String value() default "";

/** @deprecated */
@Deprecated
String serviceId() default "";

String contextId() default "";

@AliasFor("value")
String name() default "";

String qualifier() default "";

String url() default "";

boolean decode404() default false;

Class<?>[] configuration() default {};

Class<?> fallback() default void.class;

Class<?> fallbackFactory() default void.class;

String path() default "";

boolean primary() default true;
}

​@EnableFeignClients​​​ 是关于注解扫描的配置,比如扫描路径,配置等。​​@FeignClient​​​ 则是关于对该接口进行代理的时候,一些实现细节的配置,比如访问url是什么, ​​fallback​​方法,关于404的请求是抛错误还是正常返回。

3.注册client

先关注对​​EnableFeignClients​​​ 的处理,可以看出它使用了​​@Import(FeignClientsRegistrar.class)​​​,看名字可知是一个注册器,通过扫描某个特性的类,将​​bean​​​注册到IOC中。Spring 通过调用其 ​​registerBeanDefinitions​​​ 方法来获取其提供的 ​​bean definition​​。

  • FeignClientsRegistrar类
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//注册configuration
this.registerDefaultConfiguration(metadata, registry);
//注册注解
this.registerFeignClients(metadata, registry);
}
private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
//获取注解@EnableFeignClients 下设置的属性值
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
//判断传入的defaultConfiguration的是不是topClass,所谓topClass就是说此类不是别的类的内部类
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,defaultAttrs.get("defaultConfiguration"));
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {
//加载FeignClientSpecification bean
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
//注册
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),builder.getBeanDefinition());
}

这里会往 ​​Registry​​​ 里面添加一个​​BeanDefinition​​​,即 ​​FeignClientSpecification​​​,​​configuration​​​是通过 ​​EnableFeignClients​​​ 注解的 ​​defaultConfiguration​​ 参数传入。

  • registerFeignClients方法
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());
// 扫描带有FeignClient注解的类
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
//获取@EnableFeignClients 中clients的值
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
//如果没有设置,那么加入要扫描的注解和扫描的包
scanner.addIncludeFilter(annotationTypeFilter);
// 确定扫描的包路径列表
basePackages = getBasePackages(metadata);
}
else {
//如果设置了,最终扫出来的Bean必须是注解中设置的那些
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
//循环扫描,并把根据注解信息,进行相关注册
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();
//必须注解在interface上
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);
}
}
}
}

private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
//将属性设置到FeignClientFactoryBean 中
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
//设置Autowire 类型
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;
}
//注册bean
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

另一个往 Registry 里面添加的 ​​BeanDefinition​​​则是​​FeignClientFactoryBean​​​,负责注册​​FeignClient​​​。
也就是说,Feign的注册一共分为一下几步:

  1. 扫描​​@EnableFeignClients​​​注解,如果有​​defaultConfiguration​​​属性配置,则将​​configuration​​​注册到​​BeanDefinition​​​中,如果不指定的话,spring 提供的默认配置是​​FeignClientsConfiguration​​。
  2. 扫描​​basePackage​​​ 下面所有包含了​​@FeignClient​​ 注解的类
  3. 如果​​@EnableFeignClients​​​中配置了​​clients​​​属性,则扫描出来的​​bean​​​只有在​​clients​​中配置的那些
  4. 循环扫描​​@FeignClient​​​注解,如果配置了​​configuration​​​,则将​​configuration​​​按照 1 注册到​​BeanDefinition​​​中,也就是说​​Feign​​​既支持用作统一的默认的​​Config​​​作为全局配置,也可以分别在​​@FeignClien​​​t中单独配置​​configuration​​ 作为局部配置。
  5. 将​​@FeignClient​​​中的其他配置设置到​​FeignClientFactoryBean​​中。
  6. 最后调用​​FeignClientFactoryBean​​​#​​getObject​​来创建client实例。

4. FeignClientFactoryBean

FeignClientFactoryBean会生成一个@FeignClient注解的对应的service实例。

位置:​​org.springframework.cloud.openfeign.FeignClientFactoryBean​

public Object getObject() throws Exception {
return this.getTarget();
}

<T> T getTarget() {
FeignContext context = (FeignContext)this.applicationContext.getBean(FeignContext.class);
Builder builder = this.feign(context);
if (!StringUtils.hasText(this.url)) {
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();
}

if (client instanceof FeignBlockingLoadBalancerClient) {
client = ((FeignBlockingLoadBalancerClient)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));
}
}

​FeignClientFactoryBean​​​实现了​​FactoryBean​​​,我们都知道,在spring中实现了​​FactoryBean​​​的类在spring生命周期过程中,​​spring​​​会把 ​​getObject()​​​ 返回的实例注册到​​ioc​​容器。方便以后实例的调用。

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?");
}
}

在​​spring​​​生命周期过程中会调用​​getObject()​​​,从ioc容器中获取到的​​client​​​就是第一步注册到ioc容器的负载均衡实例​​LoadBalancerFeignClient​​​,通过​​builder.client(client)​​​注册到​​Feign.builder​​​中。
Spring Cloud OpenFeign源码分析_restful
现在我们可以分析最后一行​​​return targeter.target(this, builder, context, target)​​​调用
Spring Cloud OpenFeign源码分析_spring_02
Spring Cloud OpenFeign源码分析_java_03
​​​FeignClientFactoryBean​​​的​​getObject​​​最终要初始化的实例。可以明显的看到创建了一个关于​​SynchronousMethodHandler.Factory​​​的实例。第一个client参数就是上面已经注册好的​​LoadBalancerFeignClient​​的实例。

​SynchronousMethodHandler​​​ 是个拦截器。项目初始话过程中,会为所有加了​​@FeignClient​​​的service接口结合​​FeignClientFactoryBean​​​生成对应接口的代理对象,客户端调用接口时会调用​​SynchronousMethodHandler​​​的​​invoke​​​方法。
Spring Cloud OpenFeign源码分析_java_04
Spring Cloud OpenFeign源码分析_负载均衡_05
可以看出调用​​​servcie​​​接口时是通过 ​​client.execute(request, options)​​​ 调用的。client即为上面已经注册好的​​LoadBalancerFeignClient​​​的实例。
Spring Cloud OpenFeign源码分析_java_06

5. 探究client调用时如何做负载的?默认调用的哪种策略的负载?

跟进​​executeWithLoadBalancer​​​方法。
Spring Cloud OpenFeign源码分析_java_07Spring Cloud OpenFeign源码分析_负载均衡_08

跟进​​submit​​​方法,可以看到图3.3中server为null的时候,执行​​selectServer​​​()方法。跟进​​selectServer​​()。

Spring Cloud OpenFeign源码分析_负载均衡_09
跟进​​​getServerFromLoadBalancer​​​()。
Spring Cloud OpenFeign源码分析_负载均衡_10
继续跟进,可以看到 ​​​ILoadBalancer lb = getLoadBalancer()​​​,可以获取到要负载的对应接口的服务列表。​​ILoadBalancer​​主要就是一个负载均衡器的接口,作用就是从被负载的服务列表里选出一个服务去调用。

下图为​​ILoadBalancer​​​的实现类。默认调用的是​​ZoneAwareLoadBalancer​​​。
Spring Cloud OpenFeign源码分析_spring_11

Spring Cloud OpenFeign源码分析_java_12
​​​Server svc = lb.chooseServer(loadBalancerKey)​​​,是从服务列表里面选择一个服务去调用。跟进​​chooseServer​​​()方法
Spring Cloud OpenFeign源码分析_java_13
Spring Cloud OpenFeign源码分析_restful_14
继续跟进​​​super.chooseServer(key)​​​方法。红框中的​​rule​​​有多种实现。图3.7调用的时候默认调用的是​​RoundRobinRule​​​。即轮询策略。至此,我们已探究出​​FeignClient​​​默认情况下会有负载均衡,且用的是​​netflix​​​的​​ribbon​​​的轮询策略。
Spring Cloud OpenFeign源码分析_java_15
Spring Cloud OpenFeign源码分析_spring_16
Spring Cloud OpenFeign源码分析_java_17

6.如何自定义配置来修改默认的负载均衡策略呢?

位置:​​org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration​​。

加载 ​​ribbonLoadBalancer​​​ 方法时会从​​ioc​​​容器中查找注册好的Rule来通过参数注入到​​ZoneAwareLoadBalancer​​​实例中。如果使用默认的,此时的rule默认使用的是​​ZoneAvoidanceRule​​​,即依赖的rule为​​RoundRobinRule roundRobinRule = new RoundRobinRule()​​。

所以如果想定义一个需要的rule,只需要添加以下配置就可以了。

上述配置代码添加后会注册到ioc容器,下图代码为初始化 ​​ILoadBalancer​​ 实例时通过参数注入的方式 把ioc容器中注入好的rule实例传递给这个方法,完成负载策略的实现。

Spring Cloud OpenFeign源码分析_restful_18