一.OpenFeign介绍
OpenFeign是实现微服务间调用的工具,功能包括编解码、构造http请求等。同时OpenFeign又集成了ribbon功能实现客户端负载均衡能力,Bibbon默认的客户端负载均衡能力是与Eurake集成,Nacos通过重写ribbon的ServerList功能实现ribbon与Nacos集成。
二.OpenFeign初始化原理
OpenFeign入口在@EnableFeignClients注解,同时它又导入了FeignClientsRegistrar类
FeignClientsRegistrar类实现了ImportBeanDefinitionRegistrar类,这个类是Spring的一个扩展点,可以注册BeanDefinition,对外暴露了一个接口
@Override public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {registerDefaultConfiguration(metadata, registry);registerFeignClients(metadata, registry); }
再进入registerFeignClients(metadata, registry)方法
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);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<String> clientClasses = new HashSet<>();basePackages = new HashSet<>();for (Class<?> clazz : clients) {basePackages.add(ClassUtils.getPackageName(clazz));clientClasses.add(clazz.getCanonicalName());}AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {@Overrideprotected 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 interfaceAnnotatedBeanDefinition 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);}}}
}
又进入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);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 nullbeanDefinition.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);
}
这个方法是整个OpenFeign初始化客户端的核心,它注册了一个FeignClientFactoryBean类,这个类实现了FactoryBean接口,它是Spring中一个特殊的类,它可以返回“一类”对象,主要方法是getObject方法
@Override
public Object getObject() throws Exception {FeignContext context = applicationContext.getBean(FeignContext.class);Feign.Builder builder = feign(context);if (!StringUtils.hasText(this.url)) {String url;if (!this.name.startsWith("http")) {url = "http://" + this.name;}else {url = this.name;}url += cleanPath();return loadBalance(builder, context, new HardCodedTarget<>(this.type,this.name, 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 unwrapclient = ((LoadBalancerFeignClient)client).getDelegate();}builder.client(client);}Targeter targeter = get(context, Targeter.class);return targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url));
}
三.OpenFeign调用原理
每次当我们注入Feign的接口时,Spring都会为我们创建一个接口的代理,而这个代理的生成都会走getObject方法。当我们没有为接口配置具体url时,使用服务名查找,而服务名查找就会走负载均衡方法,也就是loadBalance方法
这个方法中的client实现为LoadBalanceFeignClient类
进入target方法
最终生成代理
最终执行invoke方法
使用LoadBalancerFeignClient的实现
最终调用AbstractLoadBalancerAwareClient中的executeWithLoadBalancer方法,此时已经ribbon的方法了。
进入executeWithLoadBalancer方法
进入submit方法,注意看selectServer()方法
private Observable<Server> selectServer() {return Observable.create(new OnSubscribe<Server>() {@Overridepublic void call(Subscriber<? super Server> next) {try {Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey); next.onNext(server);next.onCompleted();} catch (Exception e) {next.onError(e);}}});
}
这里就到了客户端负载均衡选择服务的地方了这里面有个细节是代码无法跟到的,在选择服务器,Nacos重写了ribbon的ServerList,这就是为什么ribbon可以读到Nacos注册中心的服务。我们看下Nacos自动装配文件
再看下ribbon原始的自动装配文件。
除了ServerList其他类几乎没动,这就完成了ribbon和Nacos的集成,整个调用过程还有很多细节,并没有跟进,后续有机会继续跟进
源码中的一些具体实现类:
Feign.Builder builder = SentinelFeign$Builder
Client client = LoadBalancerFeignClient
Targeter targeter = HystrixTargeter
DynamicServerListLoadBalancer
ZoneAwareLoadBalancer