在springcloud架构的微服中,服务提供者与服务消费之间主要以HTTP方式进行调用,这与dubbo之间通过tcp方式调用存在一定差异,dubbo中通过接口方式进行远程调用SpringCloud中通过FeignClient定义接口实现远端调用
FeignClient主要针对服务消费者使用服务消费者通过 @EnableFeignClient,@FeignClient 来启勇和声明FeignClient 其中@EnableFeignClient作用于服务启动主类,而@FeignClient用于定义服务提供方服务名和参数
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
在@FeignClient中如果定义url而不是服务提供者的名字在调用时将不会使用loadbalance
@FeignClient("service-provider")
public interface UserFeignClient {
@GetMapping("/user")
String getUser();
}
首先看下该注解的定义其中@Import(FeignClientsRegistrar.class)将FeignClient注解的接口变成springBoot中的BeanDefinition
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
接下来看下FeignClientsRegistrar类该类实现了ImportBeanDefinitionRegistrar接口,该接口用于将beanDefinition注册到springBoot的DefaultListableBeanFactory中registerBeanDefinitions方法的作用
是用来加载@EnableFeignClients中的defaultConfiguration和@FeignClient中的configuration配置文件
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
我们重点分析registerFeignClients方法该方法是扫描带有@FeignClient的接口,而担任本次扫描工作的类为ClassPathScanningCandidateComponentProvider,通过配置annotationTypeFilter需要扫描的注解为@FeignClient
扫描前需要定义其实扫描路径,如果@EnableCleint中设置了参数clients则根据设置类进行扫描,否则使用主类包名
AnnotatedBeanDefinition后判断@Feignclient接口是否作用在interface上,如果否抛出异常接下来就是注册configuration和FeignClient,我们主要分析registerFeignClient
public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set basePackages;
Map attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
final Class>[] clients = attrs == null ? null : (Class>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
final Set 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 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 attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
registerFeignClient中我们真正的看到了注册到spring Beanfactory中beandefinition为FeignClientFactoryBean,registerFeignClient中主要对@FeignClient中的配置进行解析并放到beanDefinition中
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map 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 = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
继续分析FeignClientFactoryBean,该类并不会直接将将FeigClient Bean暴露,而是通过getObject获取FeigClient实例这也是 spring中FactoryBean与普通bean的区别,最终我们终于到了获取FeignClient实例的核心code了
在通过配置中是url参数是否配置来确定是否使用带有loadbalacne的实例,至此 FeignClient的第一部分分析完成,第二部分的分析将在下一篇文章中继续
@Override
public Object getObject() throws Exception {
return getTarget();
}
T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
return (T) loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name, this.url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,new HardCodedTarget<>(this.type, this.name, url));
}
接下来将会用两篇文章具体分析Feign+Ribbon的流程,分析过程设计的方法比较多没有逐一分析,如果感兴趣可以debug分析方法细节