springcloud学习笔记之Feign组件

目录

一、feign简介应用

      1、feign的概念

      2、feign的使用

二、feign原理分析

      1、前言

      2、@EnableFeignClients注解

  2.1、@EnableFeignClients

  2.2、FeignClientsRegistrar类

  2.3、加载feign的配置信息

  2.4、注册FeignClient

  2.5、registerFeignClient

3、@FeignClient注解

    3.1 FeignClient注解使用

    3.2 @FeignClient的属性

    3.3 @FeignClient源码分析

 



一、feign简介应用

      1、feign的概念

             Feign是一个声明性的Web服务客户端。它使编写Web服务客户端变得更容易。feigin是一种模板化,声明式的http客户端,feign可以通过注解绑定到接口上来简化Http请求访问。与当我们访问别的服务端口的时候 大部分使用httpclient等请求进行调用不同,在eureka注册的服务,我们可以使用Feign 声明接口的形式来进行相关服务的调用,并提供了失败回退(其实是Hystrix组件的使用)。Feign只是一个便利的rest框架,简化调用,最后还是通过ribbon在注册服务器中找到服务实例,然后对请求进行分配。

      2、feign的使用

  •        maven依赖          

	org.springframework.cloud
	spring-cloud-starter-feign
  •      主项目中添加@EnableFeignClients
@SpringBootApplication
//扫描项目下所有使用@FeignClient注解修饰的接口
@EnableFeignClients
@EnableEurekaClient
public class FeignApplication {

	public static void main(String[] args) {

		SpringApplication.run(FeignApplication.class, args);
	}

}
  •    使用feign调用微服务
//使用feignclien注解和SpringMVC相关的常用注解 声明一个feign接口 调用微服务信息
@FeignClient(value = "service-ribbon",url = "https://api.github.com",decode404 = false,
        fallback = MyFallback.class, fallbackFactory = MyFallBackFactory.class )
public interface SayHiFeign {

    @RequestMapping("/hi", method = RequestMethod.GET)
    String sayHi(@RequestParam(name="name",required = true) String name);
}

  如上便完成了使用feign完成别的为服务调用,是不是很简单。点击下载demo源码 feign组件使用demo

二、feign原理分析

      1、前言

                 通过如上的简单介绍我们大致了解了feign的概念以及如何在项目中进行使用,通过上面的观察以及使用,我们发现feign组件在springclound项目中使用是如此只简单:只需在项目的主启动类使用@EnableFeignClients注解,在服务接口类中使用@FeignClient修饰。下面我们就从这两个注解入手揭开feign的神秘面纱(貌似话说的有点大)。

      2、@EnableFeignClients注解

                    为了方便理解,我们在这里先不给出结论,直接从源码中一步一步分析代码,最后再给出结论。

             2.1、@EnableFeignClients

//运行时保留
@Retention(RetentionPolicy.RUNTIME)
//该注解用于修饰描述类、接口(包括注解类型) 或enum声明
@Target(ElementType.TYPE)
//可以生产api doc
@Documented
//这个比较重要使用注解修饰的项目 需要先初始化FeignClientsRegistrar该类 
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

	//basePackages的别名
	String[] value() default {};

    //指定需要扫描的包名列表
	String[] basePackages() default {};

    //指定需要扫描的class列表信息
	Class[] basePackageClasses() default {};

	//feign调用的配置信息类 该对象非常重要,
    //包含FeignClient需要的重试策略,超时策略,日志等配置,
    //如果某个服务没有设置, 则读取默认的配置。
	Class[] defaultConfiguration() default {};

	//直接指定FeignClient注解修饰的class
	Class[] clients() default {};

      该注解 用来开启 Feign,所以其属性主要是指定要扫描的包名类名 FeignClient列表(value属性,basePackages属性,basePackageClasses属性,clients属性),以及相关的配置信息(defaultConfiguration),这些并非我们关注的重点 在该注解类上有个使用@Import注解引入FeignClientsRegistrar.class 的实体类 其实该类是实现feign调用的部分功能(前期准备)的实现,下面让我们来分析一下

FeignClientsRegistrar类

   2.2、FeignClientsRegistrar类

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
      ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {

          通过名字可以看出该类是一个注册器,该类实现了 ImportBeanDefinitionRegistrar 通过扫描某个特性的类,将bean注册到IOC中。他的生命周期方法是registerBeanDefinitions ,Spring 通过调用其 registerBeanDefinitions 方法来获取其提供的 bean definition。

//生命周期的方法
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
    	BeanDefinitionRegistry registry) {
    //注册feign 相关的特性 配置信息
	registerDefaultConfiguration(metadata, registry);
    //注册FeignClient 对象
	registerFeignClients(metadata, registry);
}

上面开启feign主要分两大部分:

  •  注册feign的配置信息,比如重试机制,日志配置
  •  注册所有使用@FeigenClient修饰的类注册

 2.3、加载feign的配置信息

//注册配置信息
private void registerDefaultConfiguration(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        //获取EnableFeignClients注解的元数据信息
		Map defaultAttrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

        //判断是否存在defaultConfiguration 属性信息
		if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
			String name;
			if (metadata.hasEnclosingClass()) {
				name = "default." + metadata.getEnclosingClassName();
			}
			else {
				name = "default." + metadata.getClassName();
			}
            //注册defaultConfiguration 对应的属性信息类 ,如果为空则使用默认的配置 
            //FeignAutoConfiguration
            //最终将配置类解析成一个BeanDefinition类注入到spring中
			registerClientConfiguration(registry, name,
					defaultAttrs.get("defaultConfiguration"));
		}
	}
如上主要是从启动类的@EnableFeignClients 中获取defaultConfiguration属性中用户自定义的配置信息,如果没有则使用默认的配置信息类FeignAutoConfiguration,最终将其包装成BeanDefinition类注册到spring容器中(代码比较繁琐,个人只贴到这里)。配置中心主要提供了feign 的功能特性,该配置信息是适用于@FeignClient注解的全局配置,也可以在@FeignClient的configuration属性做单独的配置。该配置主要包含熔断器、失败重试、超时策略、日志配置等。

 2.4、注册FeignClient

   //注册FeigenClient
    public void registerFeignClients(AnnotationMetadata metadata,
                                     BeanDefinitionRegistry registry) {
        //获取类扫描组件
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        //添加资源加载服务类 提供加载指定目录下的资源信息 便于后面的类扫描
        scanner.setResourceLoader(this.resourceLoader);

        Set basePackages;

        //获取EnableFeignClients注解的属性信息 其中可能包含需要扫描的包名或者class信息
        Map attrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName());
        //创建扫描过滤规则 只扫描包含FeignClient注解修饰的类
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
                FeignClient.class);
        //获取注解中包含client 属性  如果有则不进行包名扫描 直接遍历存在的client 进行注册
        final Class[] clients = attrs == null ? null
                : (Class[]) attrs.get("clients");
        if (clients == null || clients.length == 0) {
            //为扫描类设置上面创建的只扫描FeignClient注解的扫描规则
            scanner.addIncludeFilter(annotationTypeFilter);
            //获取扫描的包名
            basePackages = getBasePackages(metadata);
        }
        else {
            //有client 将client所在的包加入的扫描包列表中 添加新的一个过滤规则
            final Set 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 FeignClientsRegistrar.AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
        }
        //遍历包扫描列表
        for (String basePackage : basePackages) {
            //通过报名获取对应的查找出来的FeignClient注解修饰的接口的ScanGeneric的 BeanDefinition描述
            Set 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注解相关的属性信息 url  name fallBack fallbackFactory decode404等
                    Map attributes = annotationMetadata
                            .getAnnotationAttributes(
                                    FeignClient.class.getCanonicalName());

                    String name = getClientName(attributes);
                    //attributes中局部配置信息注册和如上说的全局配置注册流程一致
                    registerClientConfiguration(registry, name,
                            attributes.get("configuration"));
                    //根据元数据 将使用FeignClient修饰的类解析成BeanDefinition
                    registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
    }

 registerFeignClients方法主要是通过扫描指定包下的所有使用@FeignClient修饰的接口类列表(如果@EnableFeignClients中配置了clients属性,则扫描出来的bean只有在clients中配置的那些),遍历这些类进行FeignClient的注册以及局部配置信息的注册。下面继续关注registerFeignClient()方法如何实现注册FeignClient

2.5、registerFeignClient

private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map 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 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);
	}

 这个方面比较简单 只是获取FeignClient注解的元数据将其设置到BeanDefinitionBuilder 用于构造BeanDefinition类并注册到spring中。我们需要注册的是程序获取BeanDefinition的类是FeignClientFactoryBean(该类看名字可以得出是FeignClient工程类 i因为修饰的类是接口类 所有是创建FeignClient代理 这点我们在分析@FeignClient注解的时候讲解)

补充:BeanDefinition  Spring容器启动的过程中,会将Bean解析成Spring内部的BeanDefinition结构
其不仅包含bean的香港信息 还包含其他关于描述该bean特性的信息 例如scode 懒加载等spring赋予的特性信息
里面类名、scope、属性、构造函数参数列表、依赖的bean、是否是单例类、是否是懒加载等,其实就是将Bean的定义信息存储到这个BeanDefinition相应的属性中,后面对Bean的操作就直接对BeanDefinition进行,例如拿到这个BeanDefinition后,可以根据里面的类名、构造函数、构造函数参数,使用反射进行对象创建。’

总结:

  到此开启Feign,就完成了我们总结一下

Feign的注册一共分为一下几步:

  • 扫描@EnableFeignClients注解,如果有defaultConfiguration属性配置,则将configuration注册到BeanDefinition中,如果不指定的话,spring 提供的默认配置是FeignClientsConfiguration。
  • 扫描 basePackage 下面所有包含了 FeignClient 注解的类
  • 如果@EnableFeignClients中配置了clients属性,则扫描出来的bean只有在clients中配置的那些
  • 循环扫描@FeignClient注解,如果配置了configuration,则将configuration按照 1 注册打BeanDefinition中,也就是说Feign既支持用作统一的默认的Config作为全局配置,也可以分别在@FeignClient中单独配置configuration 作为局部配置。
  • 将@FeignClient中的其他配置设置到FeignClientFactoryBean中。

3、@FeignClient注解

        3.1 FeignClient注解使用

//使用feignclien注解和SpringMVC相关的常用注解 声明一个feign接口 调用微服务信息
@FeignClient(value = "service-ribbon",url = "https://api.github.com",decode404 = false,
        fallback = MyFallback.class, fallbackFactory = MyFallBackFactory.class )
public interface SayHiFeign {

    @RequestMapping("/hi", method = RequestMethod.GET)
    String sayHi(@RequestParam(name="name",required = true) String name);
}

      3.2 @FeignClient的属性

                 属性

                             相关描述

              name/value      服务名称(在注册中心注册的服务名称)(两者其中之一必填)

                    url

     硬编码存在的url地址 如果该地址存在 则不通过服务名称查找服务
               decode404       对于404的错误 是直接抛出异常(图1),还是将异常信息解码响应(图2)
                  config       FeignClientsConfiguration feign客户端 提供encode编码器 decode解码器 contract连接器
                fallback       服务调用失败的时候会回调该类下同名的方法,用于失败的服务降级等操作
             fallbackFactory     失败回调工厂类生产fallback相关的工厂类作用与fallback一样
  •       decode404

springcloud学习笔记之Feign组件_第1张图片

springcloud学习笔记之Feign组件_第2张图片

fallback 失败回调类

/**
 *失败回调类 实现需要失败回调的接口 从而保证其有与实现类一样的方法
 */
@Component
public class MyFallback implements GitHubApiFeign {
    Logger logger = LoggerFactory.getLogger(MyFallback.class);
    @Override
    public String searchRepositories(String queryStr) {
        logger.info("失败回调操作");
        return null;
    }
    @Override
    public String searchRepositoriea(String queryStr) {
        logger.info("失败回调操作");
        return null;
    }
}
  • fallbackFactory 是被回调工厂类
/**
 * 失败回调的工厂类 没有特殊之处 只是针对上述的失败类使用工厂模式创建而已
 */
@Component
public class MyFallBackFactory implements FallbackFactory {
    private final MyFallback myFallback;
    public MyFallBackFactory(MyFallback myFallback) {
        this.myFallback = myFallback;
    }
    @Override
    public GitHubApiFeign create(Throwable cause) {
        //打印下异常
        System.out.println("失败回调的工厂");
        cause.printStackTrace();
        return myFallback;
    }
}

  3.3 @FeignClient源码分析

        在 2.5 registerFeignClient中我们发现是将Feign的包装成BeanDefinition类注册到spring容器中,同时我们也可以明确的知道该包装成BeanDefinition的类为FeignClientFactoryBean类。在上面的篇幅中我们分析了Feign如何在Spring容器中进行注册的,下面我们就从FeignClientFactoryBean来分析一下Feign的调用过程(中间可能会简单涉及Ribbon和Hystrix的介绍)。

  •  getObject()方法   

 FeignClientFactoryBean实现了FactoryBean所以该类在spring中使用的时候会调用其getObject()方法返回指定的bean对象

@Override
	public Object getObject() throws Exception {
		//从应用中获取Feign上下文环境信息 FeignContext中默认使用FeignClientsConfiguration中的配置信息
		//不如feign的Decoder解码 Encoder编码 Retryer重试次数 logger日志等 以及各种Feign HystrixFeign(带有熔断作用的Feign)等
		FeignContext context = applicationContext.getBean(FeignContext.class);
		//根据FeignContext 上下文环境以构建形式创建Feign 具体单独分析
		Feign.Builder builder = feign(context);

		//判断@FeignClient中的url属性是否有值 没有值则走Eureka注册中心
		if (!StringUtils.hasText(this.url)) {
			//如下是获取对应的请求url 如果与Eureka结合使用则是注册中心的服务名称类型的url
			//这个做url的规范化处理
			String url;
			if (!this.name.startsWith("http")) {
				url = "http://" + this.name;
			}
			else {
				url = this.name;
			}
			url += cleanPath();
			//使用负载均衡以及Hystrix 对请求进行相关的调用
			return loadBalance(builder, context, new Target.HardCodedTarget<>(this.type,
					this.name, url));
		}
		//有url属性值 则直接请求该url
		if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
			this.url = "http://" + this.url;
		}
		String url = this.url + cleanPath();
		//从Feign的上下文环境中获取Feign的client(主要用作请求的执行)
		Client client = getOptional(context, Client.class);
		//判断如果client的请求是存在且为LoadBalancerFeignClient(具备负载请求的客户端) 则将客户端转换
		//并设置入Fiegn对象中
		if (client != null) {
			if (client instanceof LoadBalancerFeignClient) {
				// not lod balancing because we have a url,
				// but ribbon is on the classpath, so unwrap
				client = ((LoadBalancerFeignClient)client).getDelegate();
			}
			builder.client(client);
		}
		//获取对应的Targeter类 该类的实现有两种一种是DefaultTargeter类(不做任何处理 仅仅是Feign target调用)
		//另一种是HystrixTargeter类 使用给feign添加了Hystrix的熔断 失败回调的功能
		Targeter targeter = get(context, Targeter.class);
		//这个是我们的核心方法 用来创建Feign的具体代理对象
		return targeter.target(this, builder, context, new Target.HardCodedTarget<>(
				this.type, this.name, url));
	}
  • Feign.builder的构建

           将Feign环境中的配置信息(编解码对象,重试 ,client客户端,Targeter)获取到以构建的形式构造Feign对象

	protected Feign.Builder feign(FeignContext context) {
		//这个的get() getOptional()方法都是从Feign的上下文中根据获取对应的对象信息
		//获取日志
		FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
		Logger logger = loggerFactory.create(this.type);

		// @formatter:off
		//设置 日志 请求的编码对象 解码对象 Contract请求的协议信息
		Feign.Builder builder = get(context, Feign.Builder.class)
				// required values
				.logger(logger)
				.encoder(get(context, Encoder.class))
				.decoder(get(context, Decoder.class))
				.contract(get(context, Contract.class));
		// @formatter:on

		//可选操作
		//设置日志等级
		Logger.Level level = getOptional(context, Logger.Level.class);
		if (level != null) {
			builder.logLevel(level);
		}
		//重试
		Retryer retryer = getOptional(context, Retryer.class);
		if (retryer != null) {
			builder.retryer(retryer);
		}
		//请求返回错误信息的解码对象
		ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
		if (errorDecoder != null) {
			builder.errorDecoder(errorDecoder);
		}
		//连接超时,读取超市的配置信息
		Request.Options options = getOptional(context, Request.Options.class);
		if (options != null) {
			builder.options(options);
		}
		//请求拦截器
		Map requestInterceptors = context.getInstances(
				this.name, RequestInterceptor.class);
		if (requestInterceptors != null) {
			builder.requestInterceptors(requestInterceptors.values());
		}
         //404请求不到情况下错误信息的编码处理
		if (decode404) {
			builder.decode404();
		}
		//构建成功返回
		return builder;
	}

    使用@FeignClient注解修饰的Feign接口被包装成Target(目标对象),被Targeter(瞄准者)所调用

  • loadBalance
protected  T loadBalance(Feign.Builder builder, FeignContext context,
                            HardCodedTarget target) {
    // 1.获取Client的实现类,默认为LoadBalancerFeignClient类
    // 实现在FeignRibbonClientAutoConfiguration中
    Client client = getOptional(context, Client.class);
    if (client != null) {
        // 2.将LoadBalancerFeignClient包装到Feign.Builder
        builder.client(client);
        
        // 3.获取ApplicationContext中的Targeter实现
        // 默认实现为HystrixTargeter,实现在FeignAutoConfiguration类中
        Targeter targeter = get(context, Targeter.class);
        
        // 4.重点在这里
        // 我们来看下这个方法
        return targeter.target(this, builder, context, target);
    }
    ...
}

 

  • Targeter 的target方法

请求的处理 有url不走负载请求,没有url 则配合Eureka使用进行请求的负载均衡,但是无论@FeignClient的url是否有值 最终处理逻辑均相似 获取Targeter并调用其中的target()方法 唯一不同是配合Eureka targeter为HystrixTarger ,直接请求url的是使用DefaultTargerter对象。

	public  T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
						Target.HardCodedTarget target) {
		//判断如果该feign 不是HystrixFeign 则直接调用
		if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
			return feign.target(target);
		}
		//是HystrixFeign 将其转换出来
		feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;

		SetterFactory setterFactory = getOptional(factory.getName(), context,
				SetterFactory.class);
		if (setterFactory != null) {
			builder.setterFactory(setterFactory);
		}
		//获取失败的回调对象 如果存在请求调用的时候设置失败回调类
		Class fallback = factory.getFallback();
		if (fallback != void.class) {
			return targetWithFallback(factory.getName(), context, target, builder, fallback);
		}
		//获取失败的回调对象的工厂类 如果存在请求调用的时候设置失败回调工厂类
		Class fallbackFactory = factory.getFallbackFactory();
		if (fallbackFactory != void.class) {
			return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
		}
        //否则直接调用
		return feign.target(target);
	}
  •     targetWithFallback和targetWithFallbackFactory方法

      在上面的分析中我们看到了是@FiegnClient 注解的属性fallback或fallbackFactory对应的调用targetWithFallback或targetWithFallbackFactory,其实两者的逻辑处理相似 前者获取其中的回调实例,后者获取其中的回调工厂实例,但是最终还都是调用 1、builder.target(target, fallback);  2、builder.target(target, fallbackFactory);

  •   builder.target() feign.target()

       如下Feign 和HystrixFeign提供众多的target方法

Hystrix 提供了四种target方法
public  T target(Target target, T fallback) {
            return this.build(fallback != null ? new feign.hystrix.FallbackFactory.Default(fallback) : null).newInstance(target);
        }

        
public  T target(Target target, FallbackFactory fallbackFactory) {
            return this.build(fallbackFactory).newInstance(target);
        }

        
public  T target(Class apiType, String url, T fallback) {
            return this.target(new HardCodedTarget(apiType, url), (Object)fallback);
        }

        
public  T target(Class apiType, String url, FallbackFactory fallbackFactory) {
            return this.target(new HardCodedTarget(apiType, url), (FallbackFactory)fallbackFactory);
        }


feign提供了五中target方法
    public  T target(Class apiType, String url) {
      return target(new HardCodedTarget(apiType, url));
    }

    public  T target(Target target) {
      return build().newInstance(target);
    }

如上这些方法都是调用其build().newInstance()方式,同时也是我们需要分析的下面我们来看一下build()方法

  public Feign build() {
      //获取SynchronousMethodHandler的工厂类 用来创建SynchronousMethodHandler对象
      //而我们所有的Feign接口方法都被包装成该对象SynchronousMethodHandler
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                                               logLevel, decode404);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder,
                                  errorDecoder, synchronousMethodHandlerFactory);
      //真正创建ReflectiveFeign 同时后面的newInstance会调用该对象的实现。
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
    }

  如上创建ReflectiveFeign对象 我们需要看一下该类的newInstance()方法。

public  T newInstance(Target target) {
		//targetToHandlersByName 见名知义 从目标对象(使用@FeignClient修饰的接口类)中获取其中所有的接口方法 放到map中
		//key 为方法的全限定名称,value 为MethodHandler的实现类 feign调用是SynchronousMethodHandler类
		//在这里其实是将feign的所有方法包SynchronousMethodHandler 调用的时候会执行其invoke()方法
		Map nameToHandler = targetToHandlersByName.apply(target);
		Map methodToHandler = new LinkedHashMap();
		List defaultMethodHandlers = new LinkedList();
        
		//遍历目标类的所有接口方法 并将其存放入methodToHandler方法处理器集合中
		for (Method method : target.type().getMethods()) {
			if (method.getDeclaringClass() == Object.class) {
				continue;
			} else 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)));
			}
		}
		//调用SynchronousMethodHandlerFactory 的create方法 创建InvocationHandler 的实现类
		//HystrixInvocationHandler(JDK动态代理)
		InvocationHandler handler = factory.create(target, methodToHandler);
		//创建代理对象
		T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);

		for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
			defaultMethodHandler.bindTo(proxy);
		}
		//将创建的代理对象返回
		return proxy;
	}

   如上代码将为feign接口使用jdk动态代理创建了一个代理对象HystrixInvocationHandler。所以相关的对feign接口的调用触发HystrixInvocationHandler对象的invoke()方法,分析到这里我们对feign的调用逻辑大致清晰了,所有Feign接口类会使用创建Feign的代理对象,所有接口方法调用都 HystrixInvocationHandler的invoke()方法

  • HystrixInvocationHandler的invoke()方法

	public Object invoke(final Object proxy, final Method method, final Object[] args)
			throws Throwable {
		// early exit if the invoked method is from java.lang.Object
		// code is the same as ReflectiveFeign.FeignInvocationHandler
		//equals hashCode toString 单独设置
		if ("equals".equals(method.getName())) {
			try {
				Object otherHandler =
						args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
				return equals(otherHandler);
			} catch (IllegalArgumentException e) {
				return false;
			}
		} else if ("hashCode".equals(method.getName())) {
			return hashCode();
		} else if ("toString".equals(method.getName())) {
			return toString();
		}
        //对于feign接口请求 结合Hystrix组件 使用新建线程来单独执行,防止服务之间调用时候某些服务不可用时候的服务雪崩
		HystrixCommand hystrixCommand = new HystrixCommand(setterMethodMap.get(method)) {
			@Override
			protected Object run() throws Exception {
				try {
					//dispatch的类为Map 包含一个Figen接口中的所有接口方法
					// 这些接口方法被包装成了MethodHandler 最终该童虎方法名获取到对应的MethodHanlder 调用其invoke()方法
					return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
				} catch (Exception e) {
					throw e;
				} catch (Throwable t) {
					throw (Error) t;
				}
			}

			//失败回调
			@Override
			protected Object getFallback() {
				....
				try {
					Object fallback = fallbackFactory.create(getExecutionException());
					//失败回调方法被封装成了MethodHandler 如果正常调用是被则 调用失败的MethodHandler的invoke方法
					Object result = fallbackMethodMap.get(method).invoke(fallback, args);
				....
			} 
  

 如上Feign接口调用是其实是根据方法名获取对应的MethodHandler类确定的说是SynchronousMethodHandler类的invoke()

同时该方法的调用是通过Hystrix组件来实现每个方法调用都创建线程来单独调用。

下面来看一下SynchronousMethodHandler的invoke()方法

	public Object invoke(Object[] argv) throws Throwable {
		//feign之间的调用是基于HTTP请求 所以在创建Resquest请求
		RequestTemplate template = buildTemplateFromArgs.create(argv);
		Retryer retryer = this.retryer.clone();
		while (true) {
			try {
				//执行请求()
				return executeAndDecode(template);
			} catch (RetryableException e) {
				retryer.continueOrPropagate(e);
				if (logLevel != Logger.Level.NONE) {
					logger.logRetry(metadata.configKey(), logLevel);
				}
				continue;
			}
		}
	}

	Object executeAndDecode(RequestTemplate template) throws Throwable {
		//获取请求信息
		Request request = targetRequest(template);

		if (logLevel != Logger.Level.NONE) {
			logger.logRequest(metadata.configKey(), logLevel, request);
		}

		Response response;
		long start = System.nanoTime();
		try {
			//使用客户端执行请求 Feign默认集成了Ribbion 所以这里请求是带有负载均衡功能的
			response = client.execute(request, options);
			// ensure the request is set. TODO: remove in Feign 10
			response.toBuilder().request(request).build();
			//后面是将响应信息解码封装 然后返回
			.....
	}

  如上invoke()调用其实是封装http请求 并发送请求,又因为这里feign和Ribbon默认集成,所以请求是带有负载功能的请求。到此feign组件分析结束。

  • 总结
  1.        @EnableFeignClients将所有使用@FeignClientFeignClientFactoryBean 在spring注册为BeanDefinition类,则使用初始化该类调用其getObject()方法
  2.     使用JDK动态代理针对Feign接口生成代理对象
  3.    中间会有和Hystrix、Ribbon调用。
  4.  具体方法的调用是MethodHandler的invoke()

 

   Feign分析到此结束,个人能力有限,有不足之处,希望大家不吝赐教,如果帮到你的话,请点赞或评论,你的支持是我继续更博的动力。同时也谢谢你这么好看还来看我的文章。

  

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(spring,cloud)