文章目录
- 1.如何使用spring cloud feign
- 2.spring cloud feign是如何工作的
- 3.注册client
- 4. FeignClientFactoryBean
- 5. 探究client调用时如何做负载的?默认调用的哪种策略的负载?
- 6.如何自定义配置来修改默认的负载均衡策略呢?
1.如何使用spring cloud feign
- 导入依赖
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 开始FeignClient
public class AdminWebApp {
public static void main(String[] args) {
SpringApplication.run(AdminWebApp.class,args);
}
}
- FeignClient接口
public interface ItemCatClient {
}
从官网的例子中,可以看出是通过注解驱动的,所以从注解开始看起。
2.spring cloud feign是如何工作的
Feign涉及了两个注解,一个是@EnableFeignClients,用来开启 Feign,另一个是@FeignClient,用来标记要用 Feign 来拦截的请求接口。
- @EnableFeignClients注解:
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;
(RetentionPolicy.RUNTIME)
({ElementType.TYPE})
({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
- @FeignClient注解:
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;
({ElementType.TYPE})
(RetentionPolicy.RUNTIME)
public @interface FeignClient {
("name")
String value() default "";
/** @deprecated */
String serviceId() default "";
String contextId() default "";
("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类
//注册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方法
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() {
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的注册一共分为一下几步:
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中。
现在我们可以分析最后一行return targeter.target(this, builder, context, target)调用
FeignClientFactoryBean的getObject最终要初始化的实例。可以明显的看到创建了一个关于SynchronousMethodHandler.Factory的实例。第一个client参数就是上面已经注册好的LoadBalancerFeignClient的实例。
SynchronousMethodHandler 是个拦截器。项目初始话过程中,会为所有加了@FeignClient的service接口结合FeignClientFactoryBean生成对应接口的代理对象,客户端调用接口时会调用SynchronousMethodHandler的invoke方法。
可以看出调用servcie接口时是通过 client.execute(request, options) 调用的。client即为上面已经注册好的LoadBalancerFeignClient的实例。
5. 探究client调用时如何做负载的?默认调用的哪种策略的负载?
跟进executeWithLoadBalancer方法。
跟进submit方法,可以看到图3.3中server为null的时候,执行selectServer()方法。跟进selectServer()。
跟进getServerFromLoadBalancer()。
继续跟进,可以看到 ILoadBalancer lb = getLoadBalancer(),可以获取到要负载的对应接口的服务列表。ILoadBalancer主要就是一个负载均衡器的接口,作用就是从被负载的服务列表里选出一个服务去调用。
下图为ILoadBalancer的实现类。默认调用的是ZoneAwareLoadBalancer。
Server svc = lb.chooseServer(loadBalancerKey),是从服务列表里面选择一个服务去调用。跟进chooseServer()方法
继续跟进super.chooseServer(key)方法。红框中的rule有多种实现。图3.7调用的时候默认调用的是RoundRobinRule。即轮询策略。至此,我们已探究出FeignClient默认情况下会有负载均衡,且用的是netflix的ribbon的轮询策略。
6.如何自定义配置来修改默认的负载均衡策略呢?
位置:org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration。
加载 ribbonLoadBalancer 方法时会从ioc容器中查找注册好的Rule来通过参数注入到ZoneAwareLoadBalancer实例中。如果使用默认的,此时的rule默认使用的是ZoneAvoidanceRule,即依赖的rule为RoundRobinRule roundRobinRule = new RoundRobinRule()。
所以如果想定义一个需要的rule,只需要添加以下配置就可以了。
上述配置代码添加后会注册到ioc容器,下图代码为初始化 ILoadBalancer 实例时通过参数注入的方式 把ioc容器中注入好的rule实例传递给这个方法,完成负载策略的实现。