SpringCloud系列:FeignClient源码深度解析
概述
springCloud feign主要对netflix feign进行了增强和包装,本篇从源码角度带你过一遍装配流程,揭开feign底层的神秘面纱。主要包括feign整合ribbon,hystrix,sleuth,以及生成的代理类最终注入到spring容器的过程。篇幅略长,耐心读完,相信你会有所收获。
Feign架构图
一些核心类及大致流程:

大体步骤: 一、注册FeignClient配置类和FeignClient BeanDefinition 二、实例化Feign上下文对象FeignContext 三、创建Feign.builder 对象四、生成负载均衡代理类五、生成默认代理类六、注入到spring容器
源码分析
主要围绕上面6个步骤详细分析。
一、注册FeignClient配置类和FeignClient BeanDefinition
从启动类注解开始,来看下 @EnableFeignClients注解:
1. @EnableFeignClients
2. publicclassMyApplication{
3. }
这是在启动类开启feign装配的注解,跟进该注解,看看做了什么:
1. @Import(FeignClientsRegistrar.class)
1. publicclassFeignClientsRegistrarimplementsImportBeanDefinitionRegistrar,
2. ResourceLoaderAware, BeanClassLoaderAware{
3.
4. // patterned after Spring Integration IntegrationComponentScanRegistrar
5. // and RibbonClientsConfigurationRegistgrar
6. privatefinalLoggerlogger = LoggerFactory.getLogger(FeignClientsRegistrar.class);
7. privateResourceLoaderresourceLoader;
8.
9. privateClassLoaderclassLoader;
10.
11. publicFeignClientsRegistrar() {
12. }
13.
14. @Override
15. publicvoidsetResourceLoader(ResourceLoaderresourceLoader) {
16. this.resourceLoader = resourceLoader;
17. }
18.
19. @Override
20. publicvoidsetBeanClassLoader(ClassLoaderclassLoader) {
21. this.classLoader = classLoader;
22. }
23.
24. @Override
25. publicvoidregisterBeanDefinitions(AnnotationMetadatametadata,
26. BeanDefinitionRegistryregistry) {
27. //1、先注册默认配置
28. registerDefaultConfiguration(metadata, registry);
29. //2、注册所有的feignClient beanDefinition
30. registerFeignClients(metadata, registry);
31. }
32. //...
33.}
我们分别来看一下上面 registerBeanDefinitions中的两个方法: 1)注册默认配置方法: registerDefaultConfiguration:
1. privatevoidregisterDefaultConfiguration(AnnotationMetadatametadata,
2. BeanDefinitionRegistryregistry) {
3. Map<String, Object> defaultAttrs = metadata
4. .getAnnotationAttributes(EnableFeignClients.class.getName(),true);
5.
6. if(defaultAttrs != null&& defaultAttrs.containsKey("defaultConfiguration")) {
7. Stringname;
8. if(metadata.hasEnclosingClass()) {
9. name = "default."+ metadata.getEnclosingClassName();
10. }
11. else{
12. name = "default."+ metadata.getClassName();
13. }
14. // name 默认以default 开头,后续会根据名称选择配置
15. registerClientConfiguration(registry, name,
16. defaultAttrs.get("defaultConfiguration"));
17. }
18. }
上述方法为读取启动类上面 @EnableFeignClients注解中声明feign相关配置类,默认name为default,一般情况下无需配置。用默认的 FeignAutoConfiguration即可。上面有个比较重要的方法:注册配置 registerClientConfiguration,启动流程一共有两处读取feign的配置类,这是第一处。根据该方法看一下:
1. privatevoidregisterClientConfiguration(BeanDefinitionRegistryregistry, Objectname,
2. Objectconfiguration) {
3. BeanDefinitionBuilderbuilder = BeanDefinitionBuilder
4. .genericBeanDefinition(FeignClientSpecification.class);
5. builder.addConstructorArgValue(name);
6. builder.addConstructorArgValue(configuration);
7. registry.registerBeanDefinition(
8. name + "."+FeignClientSpecification.class.getSimpleName(),
9. builder.getBeanDefinition());
10. }
上面将bean配置类包装成 FeignClientSpecification,注入到容器。该对象非常重要,包含FeignClient需要的重试策略,超时策略,日志等配置,如果某个服务没有设置,则读取默认的配置。
2、扫描FeignClient
该方法主要是扫描类路径,对所有的FeignClient生成对应的 BeanDefinition:
1. publicvoidregisterFeignClients(AnnotationMetadatametadata,
2. BeanDefinitionRegistryregistry) {
3.
4. //...
5. //获取扫描目录下面所有的bean deanDefinition
6. for(StringbasePackage : basePackages) {
7. Set<BeanDefinition> candidateComponents = scanner
8. .findCandidateComponents(basePackage);
9. for(BeanDefinitioncandidateComponent : candidateComponents) {
10. if(candidateComponent instanceofAnnotatedBeanDefinition) {
11. // verify annotated class is an interface
12. AnnotatedBeanDefinitionbeanDefinition = (AnnotatedBeanDefinition) candidateComponent;
13. AnnotationMetadataannotationMetadata = beanDefinition.getMetadata();
14. Assert.isTrue(annotationMetadata.isInterface(),
15. "@FeignClient can only be specified on an interface");
16.
17. Map<String, Object> attributes = annotationMetadata
18. .getAnnotationAttributes(
19. FeignClient.class.getCanonicalName());
20.
21. Stringname = getClientName(attributes);
22. //这里是第二处
23. registerClientConfiguration(registry, name,
24. attributes.get("configuration"));
25.
26. //注册feignClient
27. registerFeignClient(registry, annotationMetadata, attributes);
28. }
29. }
30. }
31. }
可以看到上面又调用了 registerClientConfiguration注册配置的方法,这里是第二处调用。这里主要是将扫描的目录下,每个项目的配置类加载的容器当中。 注册到容器中,什么时候会用到呢?具体又如何使用呢?别着急,后面会有介绍。
我们先会回到继续主流程,继续看注册feignClient的方法,跟进 registerFeignClient:
1. privatevoidregisterFeignClient(BeanDefinitionRegistryregistry,
2. AnnotationMetadataannotationMetadata, Map<String, Object> attributes) {
3. StringclassName = annotationMetadata.getClassName();
4. //声明代理类名称
5. BeanDefinitionBuilderdefinition = BeanDefinitionBuilder
6. .genericBeanDefinition(FeignClientFactoryBean.class);
7. //logger.info("TEX do some replacement");
8. //attributes.put("value", ((String)attributes.get("value")).replace('_','-'));
9. validate(attributes);
10. definition.addPropertyValue("url", getUrl(attributes));
11. definition.addPropertyValue("path", getPath(attributes));
12. Stringname = getName(attributes);
13. definition.addPropertyValue("name", name);
14. definition.addPropertyValue("type", className);
15. definition.addPropertyValue("decode404", attributes.get("decode404"));
16. definition.addPropertyValue("fallback", attributes.get("fallback"));
17. definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
18.
19. Stringalias= name + "FeignClient";
20. AbstractBeanDefinitionbeanDefinition = definition.getBeanDefinition();
21. beanDefinition.setPrimary(true);
22. BeanDefinitionHolderholder = newBeanDefinitionHolder(beanDefinition, className,
23. newString[] { alias});
24. //将bean definition加入到spring容器
25. BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
26. }
划重点,上面出现了一行相当关键代码:
1. BeanDefinitionBuilderdefinition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
springCloud FeignClient其实是利用了spring的代理工厂来生成代理类,所以这里将所有的 feignClient的描述信息 BeanDefinition设定为 FeignClientFactoryBean类型,该类又继承 FactoryBean,很明显,这是一个代理类。在spring中, FactoryBean是一个工厂bean,用作创建代理bean,所以得出结论,feign将所有的feignClient bean包装成 FeignClientFactoryBean。扫描方法到此结束。
代理类什么时候会触发生成呢?在spring刷新容器时,当实例化我们的业务service时,如果发现注册了FeignClient,spring就会去实例化该FeignClient,同时会进行判断是否是代理bean,如果为代理bean,则调用 FeignClientFactoryBean的 T getObject() throws Exception;方法生成代理bean。
先来隆重介绍一下 FeignClientFactoryBean,后面四步都基于此类。
先看一下代理feignClient代理生成入口: getObject方法:
1. @Override
2. publicObjectgetObject() throwsException{
3. //二、实例化Feign上下文对象FeignContext
4. FeignContextcontext = applicationContext.getBean(FeignContext.class);
5. //三、生成builder对象,用来生成feign
6. Feign.Builderbuilder = feign(context);
7.
8. //判断生成的代理对象类型,如果url为空,则走负载均衡,生成有负载均衡功能的代理类
9. if(!StringUtils.hasText(this.url)) {
10. Stringurl;
11. if(!this.name.startsWith("http")) {
12. url = "http://"+this.name;
13. }
14. else{
15. url = this.name;
16. }
17. url += cleanPath();
18. //四、生成负载均衡代理类
19. returnloadBalance(builder, context, newHardCodedTarget<>(this.type,
20. this.name, url));
21. }
22. //如果指定了url,则生成默认的代理类
23. if(StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
24. this.url = "http://"+this.url;
25. }
26. Stringurl = this.url + cleanPath();
27. //五、生成默认代理类
28. returntargeter.target(this, builder, context, newHardCodedTarget<>(
29. this.type,this.name, url));
30. }
getObject()逻辑比较多,每一行都会做一些初始化配置,来逐步分析。
二、实例化Feign上下文对象FeignContext
上述方法中第一行便是实例化 FeignContext:
1. FeignContextcontext = applicationContext.getBean(FeignContext.class);
获取 FeignContext对象,如果没有实例化,则主动实例化,如下:

可以看到feign的配置类设置到feign的容器当中,而集合中的元素正是上面我们提到的两处调用 registerClientConfiguration方法添加进去的,前后呼应。
然而,当我们引入了 sleuth之后,获取的 feignContext确是 TraceFeignClientAutoConfiguration中配置的实例 sleuthFeignContext:

可以看到上面创建了一个 TraceFeignContext实例,因为该对象继承 FeignContext,同时又加了 @Primary注解,所以在上面第2步中通过类型获取: applicationContext.getBean(FeignContext.class);,最终拿到的是 TraceFeignContext。
三、构造 FeignBuilder
继续跟进该方法:



上述代码有两处逻辑,分别来看:
1、 Feign.Builder builder = get(context, Feign.Builder.class) ,又会有以下三种情况:
1)单独使用Feign,没有引入 sleuth、 hystrix:通过加载FeignClientsConfiguration的配置创建 Feign的静态内部类: Feign.Builder
1. @Bean
2. @Scope("prototype")
3. @ConditionalOnMissingBean
4. publicFeign.BuilderfeignBuilder(Retryerretryer) {
5. returnFeign.builder().retryer(retryer);
6. }
2)引入了 hystrix,没有引入 sleuth: 通过加载 FeignClientsConfiguration的配置创建 HystrixFeign的静态内部类: HystrixFeign.Builder
1. @Configuration
2. @ConditionalOnClass({HystrixCommand.class, HystrixFeign.class})
3. protectedstaticclassHystrixFeignConfiguration{
4. @Bean
5. @Scope("prototype")
6. @ConditionalOnMissingBean
7. @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
8. publicFeign.BuilderfeignHystrixBuilder() {
9. returnHystrixFeign.builder();
10. }
11. }
3)同时引入 hystrix 和 sleuth: 加载 TraceFeignClientAutoConfiguration的配置创建: HystrixFeign.Builder:
注意:
· TraceFeignClientAutoConfiguration的配置类加载一定是在 FeignClientsConfiguration之前(先加载先生效),而 FeignClientsConfiguration加载是通过 FeignAutoConfiguration完成的,所以上图中引入了条件注解:
1. @AutoConfigureBefore({FeignAutoConfiguration.class})
· 创建创建的 builder对象和第二种情况一下,只是做了一层包装:
1. finalclassSleuthFeignBuilder{
2.
3. privateSleuthFeignBuilder() {}
4.
5. staticFeign.Builderbuilder(Tracertracer, HttpTraceKeysInjectorkeysInjector) {
6. returnHystrixFeign.builder()
7. //各组件`client,retryer,decoder`进行增强,装饰器模式。
8. .client(newTraceFeignClient(tracer, keysInjector))
9. .retryer(newTraceFeignRetryer(tracer))
10. .decoder(newTraceFeignDecoder(tracer))
11. .errorDecoder(newTraceFeignErrorDecoder(tracer));
12. }
13.}
2、设置重试策略,log等组件Feign.builder在获取之后又分别指定了重试策略,日志级别,错误代码code等,在上一步中调用 SleuthFeignBuilder.build()时已经设置过默认值了,这里为什么要重复设置呢?
我们跟进去get()方法,一探究竟:
1. protected<T> T get(FeignContextcontext, Class<T> type) {
2. //根据name,也就是服务名称来生成builder
3. T instance = context.getInstance(this.name, type);
4. if(instance == null) {
5. thrownewIllegalStateException("No bean found of type "+ type + " for "
6. + this.name);
7. }
8. returninstance;
9. }
1. public<T> T getInstance(Stringname, Class<T> type) {
2. //这里获取AnnotationConfigApplicationContext容器
3. AnnotationConfigApplicationContextcontext = getContext(name);
4. if(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
5. type).length > 0) {
6. returncontext.getBean(type);
7. }
8. returnnull;
9. }
10.
11. privateMap<String, AnnotationConfigApplicationContext> contexts = newConcurrentHashMap<>();
12.
13. protectedAnnotationConfigApplicationContextgetContext(Stringname) {
14. if(!this.contexts.containsKey(name)) {
15. synchronized(this.contexts) {
16. if(!this.contexts.containsKey(name)) {
17. //这里创建容器createContext(name)
18. this.contexts.put(name, createContext(name));
19. }
20. }
21. }
22. returnthis.contexts.get(name);
23. }
重点来了,上述代码将FeignContext做了缓存,每个服务对应一个FeignContext,服务名作为key。继续跟进 createContext(name)方法:

可以看到上述AnnotationConfigApplicationContext容器并非spring容器,只是利用了spring刷新容器的方法来实例化配置类,以服务名作为key,配置隔离。
重点来了,上面加载配置的顺序为:先加载每个服务的配置类,然后加载启动类注解上的配置类,最后加载默认的配置类。这样做有什么好处?spring刷新容器的方法也是对所有的bean进行了缓存,如果已经创建,则不再实例化。所以优先选取每个FeignClient的配置类,最后默认的配置类兜底。
所以这也证明了 sleuth的配置一定在 feign的配置类之前加载。至此, FeignBuilder构造流程结束。
四、生成负载均衡代理类
再贴一下生成代理类的入口:
1. //判断url是否为空
2. if(!StringUtils.hasText(this.url)) {
3. //......
4. returnloadBalance(builder, context, newHardCodedTarget<>(this.type,
5. this.name, url));
6. }
7. //......
8. returntargeter.target(this, builder, context, newHardCodedTarget<>(
9. this.type,this.name, url));
这里有个重要判断:判断FeignClient声明的url是否为空,来判断具体要生成的代理类。如下:这么做有什么意义?1)如果为空,则默认走Ribbon代理,也就是这个入口,会有加载ribbon的处理。 @FeignClient("MyFeignClient") 2)如果不为空,指定url,则走默认生成代理类的方式,也就是所谓的硬编码。 @FeignClient(value = "MyFeignClient",url = "http://localhost:8081") 这样处理方便开发人员进行测试,无需关注注册中心,直接http调用,是个不错的开发小技巧。
生产环境也可以用上述第二种方式,指定域名的方式。
我们跟进 loadBalance方法:
1. protected<T> T loadBalance(Feign.Builderbuilder, FeignContextcontext,
2. HardCodedTarget<T> target) {
3. //获得FeignClient
4. Clientclient = getOptional(context, Client.class);
5. if(client != null) {
6. builder.client(client);
7. returntargeter.target(this, builder, context, target);
8. }
9. thrownewIllegalStateException(
10. "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-ribbon?");
11. }
Client client = getOptional(context, Client.class);这里会从 FeignContext上下文中获取 Client对象,该对象有三种实例,具体是哪个实现呢? ![]()

这里又会有三种情况:1)没有整合 ribbon、 sleuth:获取默认的 Client: Default实例。
2)整合了 ribbon,没有整合 sleuth: 获取 LoadBalanceFeignClient实例。

3)整合了 ribbon 和 sleuth: 会获取 TraceFeignClient实例,该实例是对 LoadBalanceFeignClient的一种包装,实现方式通过 BeanPostProcessor实现: FeignBeanPostProcessor中定义了包装逻辑:
1. @Override
2. publicObjectpostProcessBeforeInitialization(Objectbean, StringbeanName)
3. throwsBeansException{
4. returnthis.traceFeignObjectWrapper.wrap(bean);
5. }
通过 wrap方法最终返回 TraceFeignClient实例。
继续回到主流程,先来看下 Targeter接口:
1. interfaceTargeter{
2. <T> T target(FeignClientFactoryBeanfactory, Feign.Builderfeign, FeignContextcontext,
3. HardCodedTarget<T> target);
4. }
该对象定义在 FeignClientFactoryBean静静态代码块中:
1. privatestaticfinalTargetertargeter;
2.
3. static{
4. TargetertargeterToUse;
5. //判断类路径是否引入了hystrixFeign
6. if(ClassUtils.isPresent("feign.hystrix.HystrixFeign",
7. FeignClientFactoryBean.class.getClassLoader())) {
8. targeterToUse = newHystrixTargeter();
9. }
10. else{
11. targeterToUse = newDefaultTargeter();
12. }
13. targeter = targeterToUse;
14. }
这里会初始化 Targeter,该类是生成feign代理类的工具类,有两种实现,正是上面的 HystrixTargeter, DefaultTargeter。因为我们引入了 hystrix,所以 Targeter实现类为 HystrixTargeter。我们继续跟进 targeter.target方法:
1. public<T> T target(Target<T> target) {
2. returnbuild().newInstance(target);
3. }
上面通过 build()方法获取生成代理类的工具类 ReflectiveFeign,再通过 newInstance正式创建代理类。继续跟进:
1. publicFeignbuild() {
2. SynchronousMethodHandler.FactorysynchronousMethodHandlerFactory =
3. newSynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
4. logLevel, decode404);
5. ParseHandlersByNamehandlersByName =
6. newParseHandlersByName(contract, options, encoder, decoder,
7. errorDecoder, synchronousMethodHandlerFactory);
8. returnnewReflectiveFeign(handlersByName, invocationHandlerFactory);
9. }
这里会创建Feign的方法工厂 synchronousMethodHandlerFactory, Feign通过该工厂为每个方法创建一个 methodHandler,每个 methodHandler中包含Feign对应的配置: retryer、 requestInterceptors等。
继续跟进 newInstance方法:
1. public<T> T newInstance(Target<T> target) {
2. //创建所有的MethodHandler
3. Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
4. Map<Method, MethodHandler> methodToHandler = newLinkedHashMap<Method, MethodHandler>();
5. List<DefaultMethodHandler> defaultMethodHandlers = newLinkedList<DefaultMethodHandler>();
6.
7. for(Methodmethod : target.type().getMethods()) {
8. if(method.getDeclaringClass() == Object.class) {
9. continue;
10. //判断是否启用默认handler
11. } elseif(Util.isDefault(method)) {
12. DefaultMethodHandlerhandler = newDefaultMethodHandler(method);
13. defaultMethodHandlers.add(handler);
14. methodToHandler.put(method, handler);
15. } else{
16. methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
17. }
18. }
19. //创建InvocationHandler,接收请求,转发到methodHandler
20. InvocationHandlerhandler = factory.create(target, methodToHandler);
21. //生成代理类
22. T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),newClass<?>[]{target.type()}, handler);
23.
24. //将默认方法绑定到代理类
25. for(DefaultMethodHandlerdefaultMethodHandler : defaultMethodHandlers) {
26. defaultMethodHandler.bindTo(proxy);
27. }
28. returnproxy;
29. }
InvocationHandler最终创建的实例为 HystrixInvocationHandler,核心方法如下:
1. HystrixCommand<Object> hystrixCommand = newHystrixCommand<Object>(setter) {
2. @Override
3. protectedObjectrun() throwsException{
4. try{
5. returnHystrixInvocationHandler.this.dispatch.get(method).invoke(args);
6. } catch(Exceptione) {
7. throwe;
8. } catch(Throwablet) {
9. throw(Error) t;
10. }
11. }
12.
13. @Override
14. protectedObjectgetFallback() {
15. //......
16. }
17. };
整个流程:Feign调用方发起请求,发送至hystrix的HystrixInvocationHandler,通过服务名称,找到对应方法的methodHandler,methodHandler中封装了loadBalanceClient、retryer、RequestInterceptor等组件,如果引入了sleuth,这几个组件均是sleuth的包装类。然后通过以上组件构造 http请求完成整个过程。
五、生成默认代理类
理解了第四步的逻辑,生成默认代理类就很容易理解了,唯一不同点就是 client的实现类为 loadBalanceClient。
注意:不管是哪种代理类,最终发起请求还是由 Feign.Default中的 execute方法完成,默认使用 HttpUrlConnection实现。
六、注入spring容器
总结:通过 spring refresh()方法,触发 FeignClientFactoryBean.getObject()方法获得了代理类,然后完成注入 spring容器的过程。该实现方式同 Dubbo的实现方式类似,有兴趣的可以自行研究噢。
作者:阿康
- 点赞
- 收藏
- 关注作者
评论(0)