ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Spring Cloud教程 第八弹 Feign源码解读

2021-06-02 11:55:54  阅读:224  来源: 互联网

标签:FeignClient Feign name url Spring 源码 context class String


更多Spring与微服务相关的教程请戳这里 Spring与微服务教程合集

 

1、@EnableFeignClients注解的神秘之处

在使用Feign的时候,我们都知道,需要在springboot的启动类打上@EnableFeignClients注解,才能使Feign生效,那么@EnableFeignClients这个注解到底有哪些神秘之处呢,我们往下看。

首先看一下@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 {};

}

 

可以很明显地看到@Import(FeignClientsRegistrar.class),接下来看看这个类的registerBeanDefinitions方法

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
    BeanDefinitionRegistry registry) {

    //注册默认配置
	registerDefaultConfiguration(metadata, registry);
    //注册所有的FeignClient
    registerFeignClients(metadata, registry);
}

这个类实际上做了两件事情,下面我分别介绍

 

1.1、registerDefaultConfiguration注册默认配置

点进registerDefaultConfiguration方法中,如下图

 

再点进上图中红框框住的方法里面去

 

注册默认配置到此处就结束了

 

1.2、registerFeignClients注册所有的FeignClient

这一节主要来看看registerFeignClients方法,顾名思义,应该是注册所有的FeignClient

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;

        // 获取@EnableFeignClients注解的属性
		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
        
        // 声明一个Filter,用于找出所有打了@FeignClient主机的类
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);

        // 获取@EnableFeignClients注解的clients属性的值
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");

        // 如果没有配置clients属性,则走if
		if (clients == null || clients.length == 0) {
            // 将过滤器添加打扫描器上
			scanner.addIncludeFilter(annotationTypeFilter);
            // 如果不做额外配置,则默认的basePackages就是启动类所在的包
			basePackages = getBasePackages(metadata);
		}
        // 如果配置了clients属性,则走else
		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)));
		}
        
        // 遍历所有的basePackage 
		for (String basePackage : basePackages) {
            // 用扫描器在basePackage 下面并结合Filter找出BeanDefinition
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
            
            // 遍历所有的BeanDefinition
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// 校验:@FeignClient注解必须作用在接口上
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
					Assert.isTrue(annotationMetadata.isInterface(),
							"@FeignClient can only be specified on an interface");

                    //获取@FeignClient注解的属性
					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());
                    
                    // 获取FeignClient的name,具体实现细节请读者自行查看
					String name = getClientName(attributes);
                    
                    // 注册FeignClient的配置
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));
                    
                    // 注册FeignClient
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

 

可以看到,最后有两个方法,分别是:

  • registerClientConfiguration(注册FeignClient的配置) 
  • registerFeignClient(注册FeignClient)

下面分别介绍这两个方法

registerClientConfiguration方法挺简单的,就是声明了一个类型为FeignClientSpecification、name为(name + "." + FeignClientSpecification.class.getSimpleName())的bean

如果你的项目中有2个FeignClient,则最终有3个FeignClientSpecification,为什么?因为@EnableFeignCliens注解也会声明一个FeignClientSpecification,这个上面已经说过了

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
      Object configuration) {
   BeanDefinitionBuilder builder = BeanDefinitionBuilder
         .genericBeanDefinition(FeignClientSpecification.class);
   builder.addConstructorArgValue(name);
   builder.addConstructorArgValue(configuration);
   registry.registerBeanDefinition(
         name + "." + FeignClientSpecification.class.getSimpleName(),
         builder.getBeanDefinition());
}

 
 

 

下面主要看registerFeignClient方法

private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		
        // @FeignClient注解所在的接口名
        String className = annotationMetadata.getClassName();

        // 生成一个FeignClientFactoryBean的代理类
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);

		// 校验fallback属性和fallbackFactory属性的值是否合格
        validate(attributes);
        
        // 给FeignClientFactoryBean的代理类添加一系列属性
		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);
        
        // bean的别名
		String qualifier = getQualifier(attributes);
		if (StringUtils.hasText(qualifier)) {
			alias = qualifier;
		}

		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
        // 通过该工具类创建FeignClient
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

上述代码段中,有两行关键代码是下面要讲的

  • 第一个关键代码是BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
  • 第二个关键代码是最后一行代码BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

 

第二个简单点,先看第二个,点进BeanDefinitionReaderUtils.registerBeanDefinition方法里看看

public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		// Bean的名字
		String beanName = definitionHolder.getBeanName();
        // 注册bean,但不表示马上初始化Bean
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		// 注册bean的别名
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}

这个没什么好说的,就是注册了一个Bean

接下来重点看看FeignClientFactoryBean这个类

该类的声明信息如下

class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware

这里科普一下:

  • Spring 中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean 即 FactoryBean。FactoryBean跟普通Bean不同,其返回的对象不是指定类的一个实例,而是该FactoryBean的getObject方法所返回的对象,创建出来的对象是否属于单例由isSingleton中的返回决定。
  • 而实现了InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候会执行该方法

那么先看看FeignClientFactoryBean类是如何实现afterPropertiesSet方法的

@Override
	public void afterPropertiesSet() throws Exception {
		Assert.hasText(this.contextId, "Context id must be set");
		Assert.hasText(this.name, "Name must be set");
	}

这个一看就懂,没什么好说的

 

接下来重要研究一下FeignClientFactoryBean类如何实现FactoryBean接口的

 

先来看标记1,FeignContext是在哪初始化的呢?

如下图所示,是在FeignAutoConfiguration中初始化的,这里的FeignContext是feign的全局上下文,并且@EnableConfigurationProperties注解将FeignClientProperties类给初始化了

 

再看标记2,点进feign方法里面

下图中的get方法,看样子好像是要根据上下文,获取一个FeignLoggerFactory实例

 

T instance = context.getInstance(this.contextId, type);对于这行代码,context是全局上下文,this.contextId是单个@FeignClient所声明的上下文,而type是FeignLoggerFactory类的class对象

 

下面红框中的代码,应该是根据@FeignClient的名称,获取由@FeignClient声明的单个上下文,并且上下文类型是AnnotationConfigApplicationContext

 

// 存储FeignClient的上下文
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

//存储配置信息
	private Map<String, C> configurations = new ConcurrentHashMap<>();

// 根据FeignClient的name去查找,如果没有上下文则createContext,有则直接返回
protected AnnotationConfigApplicationContext getContext(String name) {
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
					this.contexts.put(name, createContext(name));
				}
			}
		}
		return this.contexts.get(name);
	}

 

然后看看createContext方法,这里面有个this.defaultConfigType属性,它的类型是FeignClientsConfiguration(注意,是FeignClients而不是FeignClient,所以这是个全局配置),关于FeignClientsConfiguration我后面再讲,但是现在我很明确的告诉你这里面配置了类型为FeignLoggerFactory的bean,刚才一系列的进栈不就是为了获取它么

protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        // 此处的this对象是FeignContext,所以如果项目中有两个FeignClient的话,则this.configurations的数量是3
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name)
					.getConfiguration()) {
                将feignClient的配置信息注册到feignContext的上下文中
				context.register(configuration);
			}
		}

        // default.开头表示全局配置,将全局配置也注册到feignClient的上下文中
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}

        // this.defaultConfigType的类型是FeignClientsConfiguration,FeignClientsConfiguration是在这里注册的
		context.register(PropertyPlaceholderAutoConfiguration.class,
				this.defaultConfigType);
		context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
				this.propertySourceName,
				Collections.<String, Object>singletonMap(this.propertyName, name)));
		if (this.parent != null) {
			// Uses Environment from parent as well as beans
			context.setParent(this.parent);
			// jdk11 issue
			// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
			context.setClassLoader(this.parent.getClassLoader());
		}
		context.setDisplayName(generateDisplayName(name));
		context.refresh();
		return context;
	}

刚才经过了一系列的方法调用,接下来一路出栈,直接到下图

 

可总算拿到FeignLoggerFactory的实例了,接下来看下图

 

上图中红框中有个configureFeign方法,该方法用于对单个feignClient进行JavaConfig配置或者yml配置文件配置的

protected void configureFeign(FeignContext context, Feign.Builder builder) {
		// 获取FeignClientProperties 类型的Bean
        FeignClientProperties properties = this.applicationContext
				.getBean(FeignClientProperties.class);
		if (properties != null) {
            // 如果配置文件优先,则走if
			if (properties.isDefaultToProperties()) {
                // JavaConfig配置
				configureUsingConfiguration(context, builder);
                // 以default开头的配置,即yml中的feign.client.config.default
				configureUsingProperties(
						properties.getConfig().get(properties.getDefaultConfig()),
						builder);
                // 以feignClient的name开头的配置,即yml中的feign.client.config.name
				configureUsingProperties(properties.getConfig().get(this.contextId),
						builder);
			}
            // 如果JavaConfig优先,则走else
			else {
				configureUsingProperties(
						properties.getConfig().get(properties.getDefaultConfig()),
						builder);
				configureUsingProperties(properties.getConfig().get(this.contextId),
						builder);
				configureUsingConfiguration(context, builder);
			}
		}
		else {
            // 如果没有FeignClientProperties 这个Bean,则使用JavaConfig进行配置
			configureUsingConfiguration(context, builder);
		}
	}

 

feign方法出栈后,再完整的看一下getTarget方法

<T> T getTarget() {
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);
        
        // 如果@FeignClient注解没有手动指定url,则走if
		if (!StringUtils.hasText(this.url)) {
			if (!this.name.startsWith("http")) {
				this.url = "http://" + this.name;
			}
			else {
				this.url = this.name;
			}
            //最终的url类似于http://service-a/service-a,两个service-a分别为name属性值和path属性值
			this.url += cleanPath();
            // 因为没有手动指定url,此时的请求是负载均衡的,所以用loadBalance方法套了一层,但loadBalance方法最终返回的还是targeter.target
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(this.type, this.name, this.url));
		}
        
        // 如果手动指定url,则走下面的代码
		if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
			this.url = "http://" + this.url;
		}
        // 最终的string url为http://localhost:8080/service-a,localhost:8080是url属性的值,而service-a是path属性的值
		String url = this.url + cleanPath();
        
        // 获取发起http请求的客户端,默认为null
		Client client = getOptional(context, Client.class);
		if (client != null) {
            //下面的代码虽然出现了loadBalancer的字眼,但是分别是因为ribbon和spring cloud loadBalancer在classpath上才打开它的,但是并不能提供负载均衡,因为是手动指定的url
			if (client instanceof LoadBalancerFeignClient) {
				client = ((LoadBalancerFeignClient) client).getDelegate();
			}
			if (client instanceof FeignBlockingLoadBalancerClient) {
				client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
			}
			builder.client(client);
		}
        //默认为HystrixTargeter
		Targeter targeter = get(context, Targeter.class);
        // 最终的返回值,即FeignClientFactoryBean的生产目标对象
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(this.type, this.name, url));
	}

 

3、FeignClientsConfiguration和FeignClientProperties

这两个类分别对应全局配置和单个配置,那么何时加载的呢?

在本文中全局搜索这两个类的名字,答案就在上面。

 

 

标签:FeignClient,Feign,name,url,Spring,源码,context,class,String
来源: https://blog.51cto.com/u_14643435/2845384

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有