Springcloud FeignClient源码分析(1)

338 阅读3分钟

SpringCloud FeignClient底层实现原理(一)

在springcloud架构的微服中,服务提供者与服务消费之间主要以HTTP方式进行调用,这与dubbo之间通过tcp方式调用存在一定差异,dubbo中通过接口方式进行远程调用SpringCloud中通过FeignClient定义接口实现远端调用

FeignClient主要针对服务消费者使用服务消费者通过 @EnableFeignClient,@FeignClient 来启用和声明FeignClient 其中@EnableFeignClient作用于服务启动主类,而@FeignClient用于定义服务提供方服务名和参数

@SpringBootApplication
@EnableFeignClients
public class SpringloudFeignApplication {
    public static void main(String[] args) { 
       SpringApplication.run(SpringcloudFeignApplication.class, args); 
   }
}

在@FeignClient中如果定义了url,而不是服务名,则不会使用loadbalance.

@FeignClient(value = "springcloud-gateway",fallbackFactory = TestFignFactory.class)
public interface TestFeign { 
       @GetMapping("/v1/cache") 
       R test();
}

@EnableFeignClients分析

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

我们可以看到@Import注解里引FeignClientsRegistrar,点击换个类

接下来看下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<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() {
				@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();
					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中我们真正的看到了注册到spring Beanfactory中beandefinition为FeignClientFactoryBean,registerFeignClient中主要对@FeignClient中的配置进行解析并放到beanDefinition中

BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);

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 = contextId + "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 });
		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();
	}

	/**
	 * @param <T> the target type of the Feign client
	 * @return a {@link Feign} client created with the specified data and the context
	 * information
	 */
	<T> 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();
			}
			builder.client(client);
		}
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(this.type, this.name, url));
	}

此方法在FeignClientFactoryBean类中。

我们可以关注一下 loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name, this.url));

Client client = getOptional(context, Client.class);

这里会拿到一个LoadBalancerFeignClient实例,它是用过feign-core里面的WEB-INF下的factories文件自动装配的.

在获取到负载均衡的client后将该client更新到Feign.Builder中,然后获取target代理类为HystrixTargeter,如果不进行任何配置默认还是会调用feign中的默认方法,接下来进入到target方法中进行分析,该方法中主要有通过jdk动态代理的方式进行最终的http request调用,主要流程为FeignInvocationHandler 获取SynchronousMethodHandler并调用SynchronousMethodHandler的invoke方法最终执行http请求的方法为executeAndDecode。

public Feign build() { 
   Factory synchronousMethodHandlerFactory = new Factory(this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy);
    ParseHandlersByName handlersByName = new ParseHandlersByName(this.contract, this.options, this.encoder, this.decoder, this.queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);  
  return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory, this.queryMapEncoder);}

接着进入SynchronousMethodHandler类,找到create方法,点击找到invoke方法和exeuteAndDecode方法。