通过上篇我们了解OpenFeign他也可以完成远程通信,但是它并不是真正义意上的RPC通信,因为他是通过封装代理来实现的,下面和以前一样,知道了怎么用就来看下他是怎么实现的。

一、思考Feign要做的事情
有了ribbon的铺垫现在看OpenFeign应该很清楚的知道,这玩意就是通过注解拿到服务名,然后通过服务名获取服务列表,进行解析和负载最终拼接出一个URI路径进行代理请求,那么他要完成这一系列动作他就要做下面几件事。

参数的解析和装载
针对指定的FeignClient,生成动态代理
针对FeignClient中的方法描述进行解析
组装出一个Request对象,发起请求
二、源码分析
看过我写的ribbon的应该清楚,如果想要找到进入源码的入口那么应该要找的是FeignClient,但是FeignClient是在哪里被解析的呢,在应用篇中我在启动类中加了个@EnableFeignClients注解,这 个注解的作用其实就是开启了一个FeignClient的扫描,那么点击启动类的@EnableFeignClients注解看下他是怎么开启FeignClient的扫描的,进去后发现里面有个@Import(FeignClientsRegistrar.class)这个FeignClientsRegistrar跟Bean的动态装载有关

点击进去有个registerBeanDefinitions方法通过名称可以知道是一个Bean的注入方法

下面我写一个简单的例子来描述他是如何实现动态加载的,学FeignClientsRegistrar类 implements ImportBeanDefinitionRegistrar接口并实现registerBeanDefinitions方法

这一步搞完后,定义一个注解,把@EnableFeignClients注解上的注解都抄过来并把@Import注解里面的类改成我们自己定义的类

然后在启动类上用上自定义的注解,那么在启动类时就可以进行一个Bean的动态装载了

通过这个概念已经很清楚源码中FeignClientsRegistrar类的FeignClientsRegistrar是怎么完成Bean的动态加载了

registerDefaultConfifiguration 方法内部从 SpringBoot 启动类上检查是否有@EnableFeignClients, 有该注解的话, 则完成 Feign 框架相关的一些配置内容注册
registerFeignClients 方法内部从 classpath 中, 扫描获得 @FeignClient 修饰的类, 将类的内容解析为 BeanDefifinition , 最终通过调用 Spring 框架中的BeanDefifinitionReaderUtils.resgisterBeanDefifinition 将解析处理过的 FeignClientBeanDeififinition 添加到 spring 容器中.
复制代码

@Override
 public void registerBeanDefinitions(AnnotationMetadata metadata,
 BeanDefinitionRegistry registry) {
 //注册默认配置信息,将EnableFeignClients的defaultConfiguration注册到Spring容器中
 registerDefaultConfiguration(metadata, registry);
 //注册FeignClients(可能有多个),@FeignClient注解的接口注册到Spring容器中。
 registerFeignClients(metadata, registry);
 }复制代码
 进入registerFeignClients(metadata, registry);这玩意是干啥的呢,在启动类中的@EnableFeignClients是可以定义多个basePackers的如果定义了多个那就要扫描FeignClients,下面就是扫描处理过程,看过spring源码的人就知道前面是什么注解解析复制代码
 public void registerFeignClients(AnnotationMetadata metadata,
 BeanDefinitionRegistry registry) {
 //获取ClassPathScanner,用于扫描类路径
 ClassPathScanningCandidateComponentProvider scanner = getScanner();
 scanner.setResourceLoader(this.resourceLoader);Set basePackages;
 //获取EnableFeignClients的所有属性
 Map<String, Object> attrs = metadata
 .getAnnotationAttributes(EnableFeignClients.class.getName());
 //构造一个AnnotationTypeFilter,构造方法参数是FeignClient,这个用于过滤出只含有FeignClient的类
 AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
 FeignClient.class);
 final Class<?>[] clients = attrs == null ? null
 : (Class<?>[]) attrs.get(“clients”);
 //EnableFeignClients的clients属性值,如果是空,则获得EnableFeignClients所在的package路径(如果没有设置basePackageClasses)
 if (clients == null || clients.length == 0) {
 scanner.addIncludeFilter(annotationTypeFilter);
 basePackages = getBasePackages(metadata);
 }
 else {
 //即EnableFeignClients的clients属性不是空,则遍历,放入集合中,
 //同时获取client所在的package路面,加入到basePacakges中;构造AbstractClassTestingTypeFilter,
 //这是增加一个过滤条件,即标FeignClient注解的接口,必须在EnableFeignClients的clients中
 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 AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
 }
 //bssePackages是解析不同basePackage路径下的FeignClient的声明
 for (String basePackage : basePackages) {
 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”);Map<String, Object> attributes = annotationMetadata
 .getAnnotationAttributes(
 FeignClient.class.getCanonicalName());String name = getClientName(attributes);
 // 遍历basePackages,获取每个package下的符合条件的类,得到对应的beanDefinition,
 //得到FeignClient的configuration值,通过FeignClientSpecification其注册到spring容器中,
 //有意思的是这里检查了FeignClient注解的类须是接口,不然会报错。
 registerClientConfiguration(registry, name,
 attributes.get(“configuration”));
 //对每一个@FeignClient进行处理registerFeignClient
 registerFeignClient(registry, annotationMetadata, attributes);
 }
 }
 }
 }

复制代码
点击registerFeignClient(registry, annotationMetadata, attributes);看下做了啥事,这里面的逻辑其实就干了一件事,就是通过BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);注入一个Bean;这个注入的过程中有个比较重要的代码BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);这是一个构造者,构造一个BeanDefinition,里面把FeignClientFactoryBean.class给传了进去

下面进入.genericBeanDefinition(FeignClientFactoryBean.class);看把 FeignClientFactoryBean.class类传进去干嘛,发现注册的Bean就是参数中自己传进来的beanClass,这个传进去的beanClass是工厂Bean

Spring Cloud FengnClient实际上是利用Spring的代理工厂来生成代理类,所以在这里地方才会把所有的FeignClient的BeanDefifinition设置为FeignClientFactoryBean类型,而FeignClientFactoryBean继承自FactoryBean,它是一个工厂Bean。在Spring中,FactoryBean是一个工厂Bean,用来创建代理Bean。工厂 Bean 是一种特殊的 Bean, 对于 Bean 的消费者来说, 他逻辑上是感知不到这个 Bean 是普通的 Bean 还是工厂 Bean, 只是按照正常的获取 Bean 方式去调用, 但工厂bean 最后返回的实例不是工厂Bean 本身, 而是执行工厂 Bean 的 getObject 逻辑返回的示例。

点击这个工厂Bean的FeignClientFactoryBean类中发现里面有个getObject()方法,这个工厂Bean就是通过这个getTarget();返回一个真正的实例

画下时序图

前面说到了在启动时会通过@EnableFeignClients去扫描所有指定路径下的@FeignClient注解声明的一个接口,然后在扫描到以后要去生成一个动态代理的类,这个动态代理的生成就是在调用getObject()时完成 ,而且getObject()又会调用他方法里面的getTarget()去完成这件事,

它从applicationContext取出FeignContext,FeignContext继承了NamedContextFactory,它是用来来统一维护feign中各个feign客户端相互隔离的上下文。他只所以能完成隔离跟他父类中的contexts()方法有很大关系
复制代码

T getTarget() {
 //FeignContext注册到容器是在FeignAutoConfiguration上完成的
 //在初始化FeignContext时,会把configurations在容器中放入FeignContext中。configurations的
 //来源就是在前面registerFeignClients方法中将@FeignClient的配置configuration。
 FeignContext context = this.applicationContext.getBean(FeignContext.class);
 Feign.Builder builder = feign(context);//构建Builder对象
 //如果url为空,则走负载均衡,生成有负载均衡功能的代理类
 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));
 }
 //如果指定了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();
 }
 if (client instanceof FeignBlockingLoadBalancerClient) {
 // not load balancing because we have a url,
 // but Spring Cloud LoadBalancer is on the classpath, so unwrap
 client = ((FeignBlockingLoadBalancerClient) 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));
 }复制代码
上面有段代码Feign.Builder builder = feign(context);是构建Builder对象
复制代码
 protected Feign.Builder feign(FeignContext context) {
 FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
 Logger logger = loggerFactory.create(this.type);
// @formatter:off
    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

    configureFeign(context, builder);

    return builder;
}

复制代码
上面的builder构造完后继续向下走,配置完Feign.Builder之后,再判断是否需要LoadBalance,如果需要,则通过LoadBalance的方法来设置。实际上他们最终调用的是Target.target()方法。

loadBalance这玩意比较重要因为他是生成具备负载均衡能力的feign客户端,为feign客户端构建起绑定负载均衡客户端Client client = (Client)this.getOptional(context, Client.class); 从上下文中获取一个Client,默认是LoadBalancerFeignClient。
它是在FeignRibbonClientAutoConfifiguration这个自动装配类中,通过Import实现的

@Import({ HttpClientFeignLoadBalancedConfiguration.class,
 OkHttpFeignLoadBalancedConfiguration.class,
 DefaultFeignLoadBalancedConfiguration.class })
 复制代码
 protected T loadBalance(Feign.Builder builder, FeignContext context,
 HardCodedTarget target) {
 //针对某一个服务的client
 Client client = getOptional(context, Client.class);
 if (client != null) {
 //将client设置进去相当于增加了客户端负载均衡解析的机制
 builder.client(client);
 Targeter targeter = get(context, Targeter.class);
 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?");
}

复制代码
点击上图的targeter.target(this, builder, context, target);因为熔断准备在后面讲,所以在选tartget的实现时选择DefaultTarget.target

点击feign.target()往下走,走到这里其实就已经到了核心逻辑了,前面不一直说动态代理吗,前面走的都是人生最长的套路,前面自己写的控制层代码通过@Resource注解注入的UserOpenFeign他最终会调用下面的方法返回一个实例,那么下面看下这newInstance()方法做了啥,发现这玩意有两个实现,至于选择哪个就要看build()返回的是什么了,向下看发现build()返回的是ReflectiveFeign,所以选第二个

这个方法是用来创建一个动态代理的方法,在生成动态代理之前,会根据Contract协议(协议解析规则,解析接口类的注解信息,解析成内部的MethodHandler的处理方式。从实现的代码中可以看到熟悉的Proxy.newProxyInstance方法产生代理类。而这里需要对每个定义的接口方法进行特定的处理实现,所以这里会出现一个MethodHandler的概念,就是对应方法级别的InvocationHandler。

复制代码
 public T newInstance(Target target) {
 //根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式
 Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
 Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
 List defaultMethodHandlers = new LinkedList();
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)));
  }
}
// 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理。
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;

}