十六.SpringCloud源码剖析-Feign源码分析_t causes npes because of some lifecycle race condi-程序员宅基地

技术标签: Feign源码  《升职加薪之SpringCloudNetflix源码剖析》  SpringCloud源码解析  

前言

Spring Cloud OpenFeign 对 Netflix Feign 进行了封装,我们通常都使用Spring Cloud OpenFeign作为服务的负载均衡,本文章主要是探讨一下OpenFeign的初始化流程,以及生成代理类注入到Spring的过程


一.Feign的基本使用

Feign是一个声明式的http客户端,使用Feign可以实现声明式REST调用,它的目的就是让Web Service调用更加简单。Feign整合了Ribbon和SpringMvc注解,这让Feign的客户端接口看起来就像一个Controller,Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。而Feign则会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理

1.导入依赖

这里使用的是spring-cloud-starter-openfeign:2.0.1.RELEASE 版本

<dependency>
  	<groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.开启Feign

通过@EnableFeignClients注解,他会去扫描指定包下的@FeignClient注解

/**
 * 支付的启动类
 * @EnableFeignClients :开启Feign支持
 */
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(value="cn.itsource.springboot.feignclient")
public class PayServerApplication1040
{
    


    public static void main( String[] args )
    {
    
        SpringApplication.run(PayServerApplication1040.class);
    }
}
3.Feign的客户端接口
@FeignClient("user-server")
public interface UserFeignClient {
    

    //订单服务来调用这个方法      http://localhost:1020/user/10
    // @GetMapping(value = "/user/{id}" )
    @RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
    User getById(@PathVariable("id")Long id);
}

通过主启动类上的@EnableFeignClients注解扫描@FeignClient标注的接口,这些接口会被代理,注入到Spring容器中

二.Feign的初始化

1.FeignAutoConfiguration Fegin的自动配置

同样是在SpringBoot启动的时候,执行自动装配流程,加载FeignAutoConfiguration自动配置类
在这里插入图片描述
看一下FeignAutoConfiguration源码

@Configuration
@ConditionalOnClass(Feign.class)
//加载Fiegn的相关配置
@EnableConfigurationProperties({
    FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
    
	//Feign的客户端配置,这里用的是@Autowired注入所有的FeignClientSpecification
	@Autowired(required = false)
	private List<FeignClientSpecification> configurations = new ArrayList<>();
	
	@Bean
	public HasFeatures feignFeature() {
    
		return HasFeatures.namedFeature("Feign", Feign.class);
	}
	//注册Feign的上下文对象,FeignContext会为每个Fiegn的客户端都创建一个Spring的ApplicationContext对象并从中提取所有的Bean
	@Bean
	public FeignContext feignContext() {
    
		FeignContext context = new FeignContext();
		context.setConfigurations(this.configurations);
		return context;
	}

FeignContext是Feign的上下文对象,FeignContext会为每个Fiegn的客户端都创建一个Spring的ApplicationContext上下文对象,并把相关的组件注册到容器中,并从中提取所有的Bean,这里的Fiegn的客户端指的是Fiegn的接口,会以@FeignClient(value=“服务名”) 注解指定的服务名作为客户端的名字。

思考一个问题,private List<FeignClientSpecification> configurations上面有个@Autowired,那FeignClientSpecification是在哪儿注册到Spring容器的呢???

FeignContext 继承了NamedContextFactory如下:

public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
    

	public FeignContext() {
    
		super(FeignClientsConfiguration.class, "feign", "feign.client.name");
	}

}

继承关系如下:
在这里插入图片描述

2.Feign的上下创建

在分析Ribbon的时候我们已经探讨过NamedContextFactory,在FeignClientAutoConfiguration注册FeignContext时,通过NamedContextFactory构造器初始化,然后调用NamedContextFactory.setConfigurations(this.configurations);方法加载配置

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
		implements DisposableBean, ApplicationContextAware {
    
	//上下文对象集合
	private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
	//配置列表
	private Map<String, C> configurations = new ConcurrentHashMap<>();
	...省略...
	//初始化	
	public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
			String propertyName) {
    
		//默认配置类,默认使用的是FeignClientsConfiguration
		this.defaultConfigType = defaultConfigType;
		//配置名,feign
		this.propertySourceName = propertySourceName;
		//feign.client.name
		this.propertyName = propertyName;
	}
	//设置配置,每个Feign的客户端接口都会对应一个配置
	public void setConfigurations(List<C> configurations) {
    
		for (C client : configurations) {
    
			this.configurations.put(client.getName(), client);
		}
	}
	//销毁工作,清除上下文对象
	public void destroy() {
    
		Collection<AnnotationConfigApplicationContext> values = this.contexts.values();
		for (AnnotationConfigApplicationContext context : values) {
    
			// This can fail, but it never throws an exception (you see stack traces
			// logged as WARN).
			context.close();
		}
		this.contexts.clear();
	}
	//根据名字获取上下文对象,如:传入服务名“user-server”获取上下文对象
	protected AnnotationConfigApplicationContext getContext(String name) {
    
		if (!this.contexts.containsKey(name)) {
    
		//如果容器集合中还没有当前客户端的上下文对象,就调用createContext方法创建客户端上下文对象
		//然后存储到contexts集合中
			synchronized (this.contexts) {
    
				if (!this.contexts.containsKey(name)) {
    
					this.contexts.put(name, createContext(name));
				}
			}
		}
		//根据客户端名字获取上下文对象
		return this.contexts.get(name);
	}
	//根据客户端名字,创建上下文
	protected AnnotationConfigApplicationContext createContext(String name) {
    
		//new一个 基于注解的ApplicationContext对象,用来装载Feign的客户端
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		//如果 configurations 中包含了客户端的配置,把配置设置到创建的上下文中
		if (this.configurations.containsKey(name)) {
    
			for (Class<?> configuration : this.configurations.get(name)
					.getConfiguration()) {
    
				context.register(configuration);
			}
		}
		//注册默认的配置“default.”到上下文中
		for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
    
			if (entry.getKey().startsWith("default.")) {
    
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
    
					context.register(configuration);
				}
			}
		}
		//注册了默认的配置类,defaultConfigType就是FeignClientConfiguration
		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);
		}
		context.setDisplayName(generateDisplayName(name));
		//刷新容器
		context.refresh();
		return context;
	}

	protected String generateDisplayName(String name) {
    
		return this.getClass().getSimpleName() + "-" + name;
	}
	
	//根据名字和类型从上下文中获取实例
	public <T> T getInstance(String name, Class<T> type) {
    
		//获取上下文
		AnnotationConfigApplicationContext context = getContext(name);
		if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
				type).length > 0) {
    
				//从上下文中获取实例
			return context.getBean(type);
		}
		return null;
	}

NamedContextFactory主要提供了

  • getContext(String name) : 根据客户端名字从 Map<String, AnnotationConfigApplicationContext> contexts 中获取上下文对象的方法,如果contexts中没有上下文会调用createContext(name) 创建上下文,然后放入contexts中再返回
  • createContext(String name) :根据客户端名字创建上下文对象,方法中分别注册了RibbonClient客户端配置类,default开头的所有默认配置,以及FeignClientsConfiguration默认配置,然后刷新容器,返回上下文对象
  • getInstance(String name, Class type) :根据名字和类型返回实例,方法先根据名字得到上下文对象然后从上下文对象中获取Bean

三.Feign的客户端注册

我们接着来看下Feign是如何对客户端接口进行注册到Spring容器中的,从@EnableFeignClients标签入手

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    

该注解导入了FeignClientsRegistrar,见名知意,它对Feign的客户端的注册器

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
		ResourceLoaderAware, EnvironmentAware {
    

	// patterned after Spring Integration IntegrationComponentScanRegistrar
	// and RibbonClientsConfigurationRegistgrar

	private ResourceLoader resourceLoader;

	private Environment environment;

	public FeignClientsRegistrar() {
    
	}

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
    
		this.resourceLoader = resourceLoader;
	}
	//registerBeanDefinitions方法是ImportBeanDefinitionRegistrar接口中的用来注册Bean到Spring容器的方法
	
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    
		//注册默认的配置
		registerDefaultConfiguration(metadata, registry);
		//注册Feign的客户端
		registerFeignClients(metadata, registry);
	}

registerBeanDefinitions调用两个方法,

  • registerDefaultConfiguration:注册默认配置,
  • registerFeignClients注册Feign的客户端
1.Feign的默认配置注册

我们先看registerDefaultConfiguration方法

 private void registerDefaultConfiguration(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
    
            //得到@EnableFeignClients注解的原始信息
        Map<String, Object> defaultAttrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName(), true);
		//是否配置了@EnableFeignClients(defaultConfiguration=)默认配置
		//使用的类型是FeignClientsConfiguration
        if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
    
            String name;
            //处理类型,以default开头
            if (metadata.hasEnclosingClass()) {
    
                name = "default." + metadata.getEnclosingClassName();
            }
            else {
    
                name = "default." + metadata.getClassName();
            }
            //注册默认配置,name 默认以 default 开头,后续会根据名称选择配置
            registerClientConfiguration(registry, name,
                    defaultAttrs.get("defaultConfiguration"));
        }
    }

上面这个方法在为@EnableFeignClients(defaultConfiguration=)默认配置类做注册,一般可以不配置该属性

 private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
            Object configuration) {
    
            //创建一个BeanDefinitionBuilder
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientSpecification.class);
                //添加参数,把配置类交给builder
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        //注册了Bean到Spring容器
        registry.registerBeanDefinition(
                name + "." + FeignClientSpecification.class.getSimpleName(),
                builder.getBeanDefinition());
    }

这里是把配置封装成FeignClientSpecification类型,然后通过BeanDefinitionRegistry注册到Spring容器中,该配置类包含了FeignClient的重试,超时,日志等配置,如服务定义配置会使用默认的配置。

还记得在FeignAutoConfiguration配置类中的private List<FeignClientSpecification> configurations吗?这是不是前后呼应起来了呢?

2.Feign的客户端注册

我们在看一下 registerFeignClients 方法

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    
		
		//ClassPathScanningCandidateComponentProvider是用来扫Feign描客户端接口的
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		//resourceLoader是用来加载类资源的
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;
		//获取@EnableFeignClients注解元数据
		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		//类型过滤器,过滤FeignClient
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
		//检查是否配置clients属性
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
    
		//默认走这里
		//如果没有配置@EnableFeignClient(clients={})属性,就获取basePackages属性
		//给扫描添加includeFilters过滤器,目的是为了扫描@FeignClient注解
			scanner.addIncludeFilter(annotationTypeFilter);
			basePackages = getBasePackages(metadata);
		}
		else {
    
			//如果配置了clients就把 clients的包添加到扫描器中
			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) {
    
		//把basePackage下的注解了@FeignClient的接口封装成BeanDefinition
			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");
					//获取@FeignClient注解上的属性
					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());
					//获取客户端名字
					String name = getClientName(attributes);
					//获取@FeignClient注解的configuration配置,对配置进行注册
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));
					//注册客户端接口feignClient
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

上面代码主要扫描了注解了@FeignClient的接口,会扫描到很多的接口,然后做2个事情

  • 将其配置类通过registerClientConfiguration注册到容器中,这个方法在刚才已经分析过了,这里是第二次调用
  • 通过registerFeignClient注册FeignClient接口到容器,内部会生成接口的代理类

继续跟进registerFeignClient

private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    
			//获取到组合的Feign接口类名
		String className = annotationMetadata.getClassName();
		//FeignClientFactoryBean是用用来创建FeignClient接口的,
		//这里要对Feign接口进行代理,使用的是FeignClientFactoryBean
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
		//校验@FeignClient相关属性
		validate(attributes);
		//封装相关的属性到definition
		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";
		//得到BeanDefinition对象,它是对Feign客户端的Bean的定义对象,基于FeignClientFactoryBean
		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 });
        //将BeanDefinition加入到spring容器
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

从上面方法可以看出,Feign的客户端是通过Spring的工厂生成代理类,Feign接口被封装成BeanDefinition,类型是FeignClientFactoryBean,也就是说Feign把所有的接口都包装成FeignClientFactoryBean

思考一个问题,这里虽然对Feign接口进行了描述,封装成FeignClientFactoryBean,那么接口实例到底在什么时候创建呢???

3.FeignClientFactoryBean 工厂

这个工厂是用来生成Feign接口的代理类的,我们看一下他是如何做的

class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
		ApplicationContextAware {
    
	/***********************************
	 * WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some lifecycle race condition.
	 ***********************************/
	//Feign接口类型
	private Class<?> type;
	//Fiegn客户端名字/请求的服务名
	private String name;
	//请求的url
	private String url;
	
	private String path;

	private boolean decode404;

	private ApplicationContext applicationContext;

	private Class<?> fallback = void.class;
	//降级工厂
	private Class<?> fallbackFactory = void.class;
	....省略....
	
	@Override
	public Object getObject() throws Exception {
    
	//获取Feign的上下文,在Feign的初始化一节有说道
		FeignContext context = applicationContext.getBean(FeignContext.class);
		//调用feign方法得到Builder构造器,用来生成feign
		//开启Hystrix使用的是HystrixFeign.Builder 
		//否则使用的是feign.Feign.Builder
		Feign.Builder builder = feign(context);
		//如果没有url,即没有指定@FeignClient(url=""),就根据服务名拼接url
		if (!StringUtils.hasText(this.url)) {
    
			String url;
			//拼接请求的url如:http://user-server
			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));
		}
		//如果配置了@FeignClient(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 targeter.target(this, builder, context, new HardCodedTarget<>(
				this.type, this.name, url));
	}

FeignClientFactoryBean实现了FactoryBean,getObject方法即是用来创建FeignClient实例的

  • 该方法授权通过ApplicationContext得到FeignContext上下文

  • 然后在从FeignContext上下文中得到Feign.Builder,它是用来构建Feign客户端代理类的

  • 然后根据Feign接口 的URL(要么通过@FeignClient(url=“”)指定,如果没指定,就根据@FeignClient(value=“服务名”)拼接) 生成负载均衡代理类

    为什么这里要判断URL呢?如果我们不指定URL会默认走Ribbon的负载均衡

    @FeignClient(value="user-server")
    

    如果指定了URL会走默认的负载均衡,相当于不走负载均衡

    @FeignClient(value="user-server",url="localhost:8989")
    

Feign.Builder

这里的Feign.Builder 是从FeignContext中得到的,我们看一下它的源码

 public static class Builder {
    
 		//请求拦截器
        private final List<RequestInterceptor> requestInterceptors = new ArrayList();
      	...省略...

        public Builder() {
    
        	//Feign的日志打印级别,默认不打印
            this.logLevel = Level.NONE;
            this.contract = new Default();
            this.client = new feign.Client.Default((SSLSocketFactory)null, (HostnameVerifier)null);
            //重试策略
            this.retryer = new feign.Retryer.Default();
            this.logger = new NoOpLogger();
            //编码器
            this.encoder = new feign.codec.Encoder.Default();
            //解码器
            this.decoder = new feign.codec.Decoder.Default();
            //错误解码器
            this.errorDecoder = new feign.codec.ErrorDecoder.Default();
            this.options = new Options();
            //方法调用工厂
            this.invocationHandlerFactory = new feign.InvocationHandlerFactory.Default();
        }
		//设置日志级别
        public Feign.Builder logLevel(Level logLevel) {
    
            this.logLevel = logLevel;
            return this;
        }

        public Feign.Builder contract(Contract contract) {
    
            this.contract = contract;
            return this;
        }

        public Feign.Builder client(Client client) {
    
            this.client = client;
            return this;
        }

        public Feign.Builder retryer(Retryer retryer) {
    
            this.retryer = retryer;
            return this;
        }

        public Feign.Builder logger(Logger logger) {
    
            this.logger = logger;
            return this;
        }

        public Feign.Builder encoder(Encoder encoder) {
    
            this.encoder = encoder;
            return this;
        }

        public Feign.Builder decoder(Decoder decoder) {
    
            this.decoder = decoder;
            return this;
        }

        public Feign.Builder mapAndDecode(ResponseMapper mapper, Decoder decoder) {
    
            this.decoder = new Feign.ResponseMappingDecoder(mapper, decoder);
            return this;
        }

        public Feign.Builder decode404() {
    
            this.decode404 = true;
            return this;
        }

        public Feign.Builder errorDecoder(ErrorDecoder errorDecoder) {
    
            this.errorDecoder = errorDecoder;
            return this;
        }

        public Feign.Builder options(Options options) {
    
            this.options = options;
            return this;
        }

        public Feign.Builder requestInterceptor(RequestInterceptor requestInterceptor) {
    
            this.requestInterceptors.add(requestInterceptor);
            return this;
        }
		//添加Feign的拦截器
        public Feign.Builder requestInterceptors(Iterable<RequestInterceptor> requestInterceptors) {
    
            this.requestInterceptors.clear();
            Iterator var2 = requestInterceptors.iterator();

            while(var2.hasNext()) {
    
                RequestInterceptor requestInterceptor = (RequestInterceptor)var2.next();
                this.requestInterceptors.add(requestInterceptor);
            }

            return this;
        }

        public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
    
            this.invocationHandlerFactory = invocationHandlerFactory;
            return this;
        }

        public <T> T target(Class<T> apiType, String url) {
    
            return this.target(new HardCodedTarget(apiType, url));
        }
		//这个方法挺重要的,它是用来构建FeignClient的代理类的
        public <T> T target(Target<T> target) {
    
            return this.build().newInstance(target);
        }
        
		//注意:这里使用的是ReflectiveFeign作为Feign的默认实现
		//创建Feign接口的代理就是ReflectiveFeign搞定的
        public Feign build() {
    
            Factory synchronousMethodHandlerFactory = new Factory(this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, this.decode404);
            ParseHandlersByName handlersByName = new ParseHandlersByName(this.contract, this.options, this.encoder, this.decoder, this.errorDecoder, synchronousMethodHandlerFactory);
            return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory);
        }
    }

Feign.Builder中封装了Feign的日志注解,编码器,解码器,拦截器等等,这些东西是在FeignClientsConfiguration中被创建的

FeignClientsConfiguration

@Configuration
public class FeignClientsConfiguration {
    
	//Feign的消息转换器
	@Autowired
	private ObjectFactory<HttpMessageConverters> messageConverters;

	@Autowired(required = false)
	private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();

	@Autowired(required = false)
	private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();

	@Autowired(required = false)
	private Logger logger;

	//解码器
	@Bean
	@ConditionalOnMissingBean
	public Decoder feignDecoder() {
    
		return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
	}
	//编码器
	@Bean
	@ConditionalOnMissingBean
	public Encoder feignEncoder() {
    
		return new SpringEncoder(this.messageConverters);
	}

	@Bean
	@ConditionalOnMissingBean
	public Contract feignContract(ConversionService feignConversionService) {
    
		return new SpringMvcContract(this.parameterProcessors, feignConversionService);
	}

	@Bean
	public FormattingConversionService feignConversionService() {
    
		FormattingConversionService conversionService = new DefaultFormattingConversionService();
		for (FeignFormatterRegistrar feignFormatterRegistrar : feignFormatterRegistrars) {
    
			feignFormatterRegistrar.registerFormatters(conversionService);
		}
		return conversionService;
	}
	//如果开启了Hystrix,那么使用的是HystrixFeign.builder();
	//而不是Feign.Builder
	@Configuration
	@ConditionalOnClass({
     HystrixCommand.class, HystrixFeign.class })
	protected static class HystrixFeignConfiguration {
    
		@Bean
		@Scope("prototype")
		@ConditionalOnMissingBean
		@ConditionalOnProperty(name = "feign.hystrix.enabled")
		public Feign.Builder feignHystrixBuilder() {
    
			return HystrixFeign.builder();
		}
	}
	//重试策略
	@Bean
	@ConditionalOnMissingBean
	public Retryer feignRetryer() {
    
		return Retryer.NEVER_RETRY;
	}
	//注册Feign.Builder
	@Bean
	@Scope("prototype")
	@ConditionalOnMissingBean
	public Feign.Builder feignBuilder(Retryer retryer) {
    
		return Feign.builder().retryer(retryer);
	}
	//日志工厂
	@Bean
	@ConditionalOnMissingBean(FeignLoggerFactory.class)
	public FeignLoggerFactory feignLoggerFactory() {
    
		return new DefaultFeignLoggerFactory(logger);
	}

}

loadBalance方法

回到getObject方法看一下loadBalance方法是如何生成负载均衡代理类的

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget<T> target) {
    
			//从上下文对象FeignContext中获取Client,默认使用的是LoadBalancerFeignClient
		Client client = getOptional(context, Client.class);
		if (client != null) {
    
			//client设置给builder
			builder.client(client);
			
			Targeter targeter = get(context, Targeter.class);
			//targeter的target方法调用了Feign的target方法
			//Feign的默认实现是ReflectiveFeign类,最终调用了ReflectiveFeign.newInstance方法创建实例
			return targeter.target(this, builder, context, target);
		}

		throw new IllegalStateException(
				"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
	}

这里会从FeignContext上下文中获取到LoadBalancerFeignClient负载客户端设置给Feign.Builder ,然后从FeignContext上下文得到Targeter,通过Targeter.target方法创建实例,底层通过Feign.newInstance方法完成实例创建,默认实现是ReflectiveFeign完成创建

4.ReflectiveFeign创建Feign代理

ReflectiveFeign通过Proxy.newProxyInstance为Feign接口生成代理

public class ReflectiveFeign extends Feign {
    
	...省略...
	//target是HardCodedTarget,封装了客户端的type,name,url
	public <T> T newInstance(Target<T> target) {
    
	//得到Feign接口中的所有的方法MethodHandler
        Map<String, MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
        //把 Map<String, MethodHandler>变成Map<Method, MethodHandler> 
        Map<Method, MethodHandler> methodToHandler = new LinkedHashMap();
        List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList();
        Method[] var5 = target.type().getMethods();
        int var6 = var5.length;

        for(int var7 = 0; var7 < var6; ++var7) {
    
            Method method = var5[var7];
            if (method.getDeclaringClass() != Object.class) {
    
                if (Util.isDefault(method)) {
    
                	//这里在添加默认的方法
                    DefaultMethodHandler handler = new DefaultMethodHandler(method);
                    defaultMethodHandlers.add(handler);
                    methodToHandler.put(method, handler);
                } else {
    
                    methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
                }
            }
        }

        InvocationHandler handler = this.factory.create(target, methodToHandler);
        //生成代理
        T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{
    target.type()}, handler);
        Iterator var12 = defaultMethodHandlers.iterator();

        while(var12.hasNext()) {
    
        	//代理类绑定默认的方法

            DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next();
            defaultMethodHandler.bindTo(proxy);
        }
		//返回代理
        return proxy;
    }

到这里,Feign的客户端的注册流程就结束了,那注册是在什么时候发生的呢?是在Spring启动过程中会调用refresh()方法,在该方法中会触发FeignClientFactoryBean.getObject()得到实例注册到Spring容器中

四.总结

在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u014494148/article/details/109641547

智能推荐

使用nginx解决浏览器跨域问题_nginx不停的xhr-程序员宅基地

文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr

在 Oracle 中配置 extproc 以访问 ST_Geometry-程序员宅基地

文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc

Linux C++ gbk转为utf-8_linux c++ gbk->utf8-程序员宅基地

文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8

IMP-00009: 导出文件异常结束-程序员宅基地

文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束

python程序员需要深入掌握的技能_Python用数据说明程序员需要掌握的技能-程序员宅基地

文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求

Spring @Service生成bean名称的规则(当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致)_@service beanname-程序员宅基地

文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname

随便推点

二叉树的各种创建方法_二叉树的建立-程序员宅基地

文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include&lt;stdio.h&gt;#include&lt;string.h&gt;#include&lt;stdlib.h&gt;#include&lt;malloc.h&gt;#include&lt;iostream&gt;#include&lt;stack&gt;#include&lt;queue&gt;using namespace std;typed_二叉树的建立

解决asp.net导出excel时中文文件名乱码_asp.net utf8 导出中文字符乱码-程序员宅基地

文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码

笔记-编译原理-实验一-词法分析器设计_对pl/0作以下修改扩充。增加单词-程序员宅基地

文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词

android adb shell 权限,android adb shell权限被拒绝-程序员宅基地

文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限

投影仪-相机标定_相机-投影仪标定-程序员宅基地

文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定

Wayland架构、渲染、硬件支持-程序员宅基地

文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland

推荐文章

热门文章

相关标签