关于java:Spring-Cloud-源码分析之OpenFeign

32次阅读

共计 24677 个字符,预计需要花费 62 分钟才能阅读完成。

OpenFeign 是一个近程客户端申请代理,它的根本作用是让开发者可能以面向接口的形式来实现近程调用,从而屏蔽底层通信的复杂性,它的具体原理如下图所示。

在明天的内容中,咱们须要详细分析 OpenFeign 它的工作原理及源码,咱们持续回到这段代码。

@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    IGoodsServiceFeignClient goodsServiceFeignClient;
    @Autowired
    IPromotionServiceFeignClient promotionServiceFeignClient;
    @Autowired
    IOrderServiceFeignClient orderServiceFeignClient;

    /**
     * 下单
     */
    @GetMapping
    public String order(){String goodsInfo=goodsServiceFeignClient.getGoodsById();
        String promotionInfo=promotionServiceFeignClient.getPromotionById();
        String result=orderServiceFeignClient.createOrder(goodsInfo,promotionInfo);
        return result;
    }
}

从这段代码中,先引出对于 OpenFeign 性能实现的思考。

  1. 申明 @FeignClient 注解的接口,如何被解析和注入的?
  2. 通过 @Autowired 依赖注入,到底是注入一个什么样的实例
  3. 基于 FeignClient 申明的接口被解析后,如何存储?
  4. 在发动办法调用时,整体的工作流程是什么样的?
  5. OpenFeign 是如何集成 Ribbon 做负载平衡解析?

带着这些疑难,开始去逐项剖析 OpenFeign 的外围源码

OpenFeign 注解扫描与解析

思考,一个被申明了 @FeignClient 注解的接口,应用 @Autowired 进行依赖注入,而最终这个接口可能失常被注入实例。

从这个后果来看,能够失去两个论断

  1. @FeignClient 申明的接口,在 Spring 容器启动时,会被解析。
  2. 因为被 Spring 容器加载的是接口,而接口又没有实现类,因而 Spring 容器解析时,会生成一个动静代理类。

EnableFeignClient

@FeignClient注解是在什么时候被解析的呢?基于咱们之前所有积攒的常识,无非就以下这几种

  • ImportSelector,批量导入 bean
  • ImportBeanDefinitionRegistrar,导入 bean 申明并进行注册
  • BeanFactoryPostProcessor,一个 bean 被装载的前后处理器

在这几个选项中,仿佛 ImportBeanDefinitionRegistrar 更适合,因为第一个是批量导入一个 bean 的 string 汇合,不适宜做动静 Bean 的申明。而 BeanFactoryPostProcessor 是一个 Bean 初始化之前和之后被调用的处理器。

而在咱们的 FeignClient 申明中,并没有 Spring 相干的注解,所以天然也不会被 Spring 容器加载和触发。

那么 @FeignClient 是在哪里被申明扫描的呢?

在集成 FeignClient 时,咱们在 SpringBoot 的 main 办法中,申明了一个注解@EnableFeignClients(basePackages = "com.gupaoedu.ms.api")。这个注解须要填写一个指定的包名。

嗯,看到这里,基本上就能猜测出,这个注解必然和 @FeignClient 注解的解析有莫大的关系。

上面这段代码是 @EnableFeignClients 注解的申明,果然看到了一个很相熟的脸孔FeignClientsRegistrar

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

FeignClientsRegistrar

FeignClientRegistrar,次要性能就是针对申明 @FeignClient 注解的接口进行扫描和注入到 IOC 容器。

class FeignClientsRegistrar
      implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {}

果然,这个类实现了 ImportBeanDefinitionRegistrar 接口

public interface ImportBeanDefinitionRegistrar {default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {this.registerBeanDefinitions(importingClassMetadata, registry);
    }

    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {}}

这个接口有两个重载的办法,用来实现 Bean 的申明和注册。

简略给大家演示一下 ImportBeanDefinitionRegistrar 的作用。

gpmall-portal 这个我的项目的 com.gupaoedu 目录下,别离创立

  1. HelloService.java
  2. GpImportBeanDefinitionRegistrar.java
  3. EnableGpRegistrar.java
  4. TestMain
  1. 定义一个须要被装载到 IOC 容器中的类 HelloService
public class HelloService {}
  1. 定义一个 Registrar 的实现,定义一个 bean,装载到 IOC 容器
public class GpImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {BeanDefinition beanDefinition=new GenericBeanDefinition();
        beanDefinition.setBeanClassName(HelloService.class.getName());
        registry.registerBeanDefinition("helloService",beanDefinition);
    }
}
  1. 定义一个注解类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {}
  1. 写一个测试类
@Configuration
@EnableGpRegistrar
public class TestMain {public static void main(String[] args) {ApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestMain.class);
        System.out.println(applicationContext.getBean(HelloService.class));

    }
}
  1. 通过后果演示能够发现,HelloService这个 bean 曾经装载到了 IOC 容器。

这就是动静装载的性能实现,它相比于 @Configuration 配置注入,会多了很多的灵活性。ok,再回到 FeignClient 的解析中来。

FeignClientsRegistrar.registerBeanDefinitions

  • registerDefaultConfiguration 办法外部从 SpringBoot 启动类上查看是否有 @EnableFeignClients, 有该注解的话,则实现 Feign 框架相干的一些配置内容注册。
  • registerFeignClients 办法外部从 classpath 中,扫描取得 @FeignClient 润饰的类,将类的内容解析为 BeanDefinition , 最终通过调用 Spring 框架中的 BeanDefinitionReaderUtils.resgisterBeanDefinition 将解析解决过的 FeignClient BeanDeifinition 增加到 spring 容器中
//BeanDefinitionReaderUtils.resgisterBeanDefinition 
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
                                    BeanDefinitionRegistry registry) {
    // 注册 @EnableFeignClients 中定义 defaultConfiguration 属性下的类,包装成 FeignClientSpecification,注册到 Spring 容器。// 在 @FeignClient 中有一个属性:configuration,这个属性是示意各个 FeignClient 自定义的配置类,前面也会通过调用 registerClientConfiguration 办法来注册成 FeignClientSpecification 到容器。// 所以,这里能够齐全了解在 @EnableFeignClients 中配置的是做为兜底的配置,在各个 @FeignClient 配置的就是自定义的状况。registerDefaultConfiguration(metadata, registry);
    registerFeignClients(metadata, registry);
}

这外面须要重点剖析的就是 registerFeignClients 办法,这个办法次要是扫描类门路下所有的 @FeignClient 注解,而后进行动静 Bean 的注入。它最终会调用 registerFeignClient 办法。

public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {registerFeignClient(registry, annotationMetadata, attributes);
}

FeignClientsRegistrar.registerFeignClients

registerFeignClients 办法的定义如下。

//# FeignClientsRegistrar.registerFeignClients
public void registerFeignClients(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
   // 获取 @EnableFeignClients 注解的元数据
   Map<String, Object> attrs = metadata
         .getAnnotationAttributes(EnableFeignClients.class.getName());
    // 获取 @EnableFeignClients 注解中的 clients 属性,能够配置 @FeignClient 申明的类,如果配置了,则须要扫描并加载。final Class<?>[] clients = attrs == null ? null
         : (Class<?>[]) attrs.get("clients");
   if (clients == null || clients.length == 0) {
       // 默认 TypeFilter 失效,这种模式会查问出许多不合乎你要求的 class 名
      ClassPathScanningCandidateComponentProvider scanner = getScanner();
      scanner.setResourceLoader(this.resourceLoader);
      scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class)); // 增加蕴含过滤的属性 @FeignClient。Set<String> basePackages = getBasePackages(metadata); // 从 @EnableFeignClients 注解中获取 basePackages 配置。for (String basePackage : basePackages) {//scanner.findCandidateComponents(basePackage) 扫描 basePackage 下的 @FeignClient 注解申明的接口
         candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); // 增加到 candidateComponents,也就是候选容器中。}
   }
   else {// 如果配置了 clients,则须要增加到 candidateComponets 中。for (Class<?> clazz : clients) {candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
      }
   }
   // 遍历候选容器列表。for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) { // 如果属于 AnnotatedBeanDefinition 实例类型
         // verify annotated class is an interface
          // 失去 @FeignClient 注解的 beanDefinition
         AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
         AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();  // 获取这个 bean 的注解元数据
         Assert.isTrue(annotationMetadata.isInterface(),
               "@FeignClient can only be specified on an interface"); 
         // 获取元数据属性
         Map<String, Object> attributes = annotationMetadata
               .getAnnotationAttributes(FeignClient.class.getCanonicalName());
         // 获取 @FeignClient 中配置的服务名称。String name = getClientName(attributes);
         registerClientConfiguration(registry, name,
               attributes.get("configuration"));

         registerFeignClient(registry, annotationMetadata, attributes);
      }
   }
}

FeignClient Bean 的注册

这个办法就是把 FeignClient 接口注册到 Spring IOC 容器,

FeignClient 是一个接口,那么这里注入到 IOC 容器中的对象是什么呢?

private void registerFeignClient(BeanDefinitionRegistry registry,
      AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {String className = annotationMetadata.getClassName();  // 获取 FeignClient 接口的类全门路
   Class clazz = ClassUtils.resolveClassName(className, null); // 加载这个接口,失去 Class 实例
    // 构建 ConfigurableBeanFactory,提供 BeanFactory 的配置能力
   ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory 
         ? (ConfigurableBeanFactory) registry : null;
    // 获取 contextId
   String contextId = getContextId(beanFactory, attributes);
   String name = getName(attributes);
    // 构建一个 FeignClient FactoryBean,这个是工厂 Bean。FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
    // 设置工厂 Bean 的相干属性
   factoryBean.setBeanFactory(beanFactory);
   factoryBean.setName(name);
   factoryBean.setContextId(contextId);
   factoryBean.setType(clazz);
    
    //BeanDefinitionBuilder 是用来构建 BeanDefinition 对象的建造器
   BeanDefinitionBuilder definition = BeanDefinitionBuilder
         .genericBeanDefinition(clazz, () -> {
             // 把 @FeignClient 注解配置中的属性设置到 FactoryBean 中。factoryBean.setUrl(getUrl(beanFactory, attributes));
            factoryBean.setPath(getPath(beanFactory, attributes));
            factoryBean.setDecode404(Boolean
                  .parseBoolean(String.valueOf(attributes.get("decode404"))));
            Object fallback = attributes.get("fallback");
            if (fallback != null) {
               factoryBean.setFallback(fallback instanceof Class
                     ? (Class<?>) fallback
                     : ClassUtils.resolveClassName(fallback.toString(), null));
            }
            Object fallbackFactory = attributes.get("fallbackFactory");
            if (fallbackFactory != null) {
               factoryBean.setFallbackFactory(fallbackFactory instanceof Class
                     ? (Class<?>) fallbackFactory
                     : ClassUtils.resolveClassName(fallbackFactory.toString(),
                           null));
            }
            return factoryBean.getObject();  //factoryBean.getObject(),基于工厂 bean 发明一个 bean 实例。});
   definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); // 设置注入模式,采纳类型注入
   definition.setLazyInit(true); // 设置提早华
   validate(attributes); 
   // 从 BeanDefinitionBuilder 中构建一个 BeanDefinition,它用来形容一个 bean 的实例定义。AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
   beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
   beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);

   // has a default, won't be null
   boolean primary = (Boolean) attributes.get("primary");

   beanDefinition.setPrimary(primary);

   String[] qualifiers = getQualifiers(attributes);
   if (ObjectUtils.isEmpty(qualifiers)) {qualifiers = new String[] {contextId + "FeignClient"};
   }
   
   BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
         qualifiers);
   BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); // 把 BeanDefinition 的这个 bean 定义注册到 IOC 容器。}

综上代码剖析,其实实现逻辑很简略。

  1. 创立一个 BeanDefinitionBuilder。
  2. 创立一个工厂 Bean,并把从 @FeignClient 注解中解析的属性设置到这个 FactoryBean 中
  3. 调用 registerBeanDefinition 注册到 IOC 容器中

对于 FactoryBean

在上述的逻辑中,咱们重点须要关注的是 FactoryBean,这个大家可能接触得会比拟少一点。

首先,须要留神的是 FactoryBean 和 BeanFactory 是不一样的,FactoryBean 是一个工厂 Bean,能够生成某一个类型 Bean 实例,它最大的一个作用是:能够让咱们自定义 Bean 的创立过程。

而,BeanFactory 是 Spring 容器中的一个根本类也是很重要的一个类,在 BeanFactory 中能够创立和治理 Spring 容器中的 Bean。

上面这段代码是 FactoryBean 接口的定义。

public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {return true;}
}

从下面的代码中咱们发现在 FactoryBean 中定义了一个 Spring Bean 的很重要的三个个性:是否单例、Bean 类型、Bean 实例,这也应该是咱们对于 Spring 中的一个 Bean 最直观的感触。

FactoryBean 自定义应用

上面咱们来模仿一下 @FeignClient 解析以及工厂 Bean 的构建过程。

  1. 先定义一个接口,这个接口能够类比为咱们下面形容的 FeignClient.
public interface IHelloService {String say();
}
  1. 接着,定义一个工厂 Bean,这个工厂 Bean 中次要是针对 IHelloService 生成动静代理。
public class DefineFactoryBean implements FactoryBean<IHelloService> {

    @Override
    public IHelloService getObject()  {IHelloService helloService=(IHelloService) Proxy.newProxyInstance(IHelloService.class.getClassLoader(),
               new Class<?>[]{IHelloService.class}, (proxy, method, args) -> {System.out.println("begin execute");
           return "Hello FactoryBean";
       });
        return helloService;
    }

    @Override
    public Class<?> getObjectType() {return IHelloService.class;}
}
  1. 通过实现 ImportBeanDefinitionRegistrar 这个接口,来动静注入 Bean 实例
public class GpImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {DefineFactoryBean factoryBean=new DefineFactoryBean();
        BeanDefinitionBuilder definition= BeanDefinitionBuilder.genericBeanDefinition(IHelloService.class,()-> factoryBean.getObject());
        BeanDefinition beanDefinition=definition.getBeanDefinition();
        registry.registerBeanDefinition("helloService",beanDefinition);
    }
}
  1. 申明一个注解,用来示意动静 bean 的注入导入。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {}
  1. 编写测试类,测试 IHelloService 这个接口的动静注入
@Configuration
@EnableGpRegistrar
public class TestMain {public static void main(String[] args) {ApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestMain.class);
        IHelloService is=applicationContext.getBean(IHelloService.class);
        System.out.println(is.say());

    }
}
  1. 运行上述的测试方法,能够看到 IHelloService 这个接口,被动静代理并且注入到了 IOC 容器。

FeignClientFactoryBean

由上述案例可知,Spring IOC 容器在注入 FactoryBean 时,会调用 FactoryBean 的 getObject()办法取得 bean 的实例。因而咱们能够依照这个思路去剖析 FeignClientFactoryBean

@Override
public Object getObject() {return getTarget();
}

构建对象 Bean 的实现代码如下,这个代码的实现较长,咱们分为几个步骤来看

Feign 上下文的构建

先来看上下文的构建逻辑,代码局部如下。

<T> T getTarget() {
   FeignContext context = beanFactory != null
         ? beanFactory.getBean(FeignContext.class)
         : applicationContext.getBean(FeignContext.class);
   Feign.Builder builder = feign(context);
}

两个要害的对象阐明:

  1. FeignContext 是全局惟一的上下文,它继承了 NamedContextFactory,它是用来来对立保护 feign 中各个 feign 客户端互相隔离的上下文,FeignContext 注册到容器是在 FeignAutoConfiguration 上实现的,代码如下!
//FeignAutoConfiguration.java

@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}

在初始化 FeignContext 时,会把 configurations 放入到 FeignContext 中。configuration示意以后被扫描到的所有 @FeignClient。

  1. Feign.Builder 用来构建 Feign 对象,基于 builder 实现上下文信息的构建,代码如下。
protected Feign.Builder feign(FeignContext context) {FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
    Logger logger = loggerFactory.create(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)); //contract 协定,用来实现模版解析(前面再详细分析)// @formatter:on

    configureFeign(context, builder);
    applyBuildCustomizers(context, builder);

    return builder;
}

从代码中能够看到,feign办法,次要是针对不同的服务提供者生成 Feign 的上下文信息,比方 loggerencoderdecoder 等。因而,从这个剖析过程中,咱们不难猜测到它的原理构造,如下图所示

父子容器隔离的实现形式如下,当调用 get 办法时,会从 context 中去获取指定 type 的实例对象。

//FeignContext.java
protected <T> T get(FeignContext context, Class<T> type) {T instance = context.getInstance(contextId, type);
    if (instance == null) {
        throw new IllegalStateException("No bean found of type" + type + "for" + contextId);
    }
    return instance;
}

接着,调用 NamedContextFactory 中的 getInstance 办法。

//NamedContextFactory.java
public <T> T getInstance(String name, Class<T> type) {
    // 依据 `name` 获取容器上下文
    AnnotationConfigApplicationContext context = this.getContext(name);

    try {
        // 再从容器上下文中获取指定类型的 bean。return context.getBean(type);
    } catch (NoSuchBeanDefinitionException var5) {return null;}
}

getContext办法依据 namecontexts容器中取得上下文对象,如果没有,则调用 createContext 创立。

protected AnnotationConfigApplicationContext getContext(String name) {if (!this.contexts.containsKey(name)) {synchronized(this.contexts) {if (!this.contexts.containsKey(name)) {this.contexts.put(name, this.createContext(name));
            }
        }
    }

    return (AnnotationConfigApplicationContext)this.contexts.get(name);
}

生成动静代理

第二个局部,如果 @FeignClient 注解中,没有配置url,也就是不走相对申请门路,则执行上面这段逻辑。

因为咱们在 @FeignClient 注解中应用的是name,所以须要执行负载平衡策略的分支逻辑。

<T> T getTarget() {
    // 省略.....
     if (!StringUtils.hasText(url)) { // 是 @FeignClient 中的一个属性,能够定义申请的相对 URL

      if (LOG.isInfoEnabled()) {
         LOG.info("For'" + name
               + "'URL not provided. Will try picking an instance via load-balancing.");
      }
      if (!name.startsWith("http")) {url = "http://" + name;}
      else {url = name;}
      url += cleanPath();
      //
      return (T) loadBalance(builder, context,
            new HardCodedTarget<>(type, name, url));
   }
    // 省略....
}

loadBalance 办法的代码实现如下,其中 Client 是 Spring Boot 主动拆卸的时候实现的,如果替换了其余的 http 协定框架,则 client 则对应为配置的协定 api。

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
                            HardCodedTarget<T> target) {
    //Feign 发送申请以及承受响应的 http client,默认是 Client.Default 的实现,能够批改成 OkHttp、HttpClient 等。Client client = getOptional(context, Client.class);
    if (client != null) {builder.client(client); // 针对以后 Feign 客户端,设置网络通信的 client
        //targeter 示意 HystrixTarger 实例,因为 Feign 能够集成 Hystrix 实现熔断,所以这里会一层包装。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 or spring-cloud-starter-loadbalancer?");
}

HystrixTarget.target代码如下,咱们没有集成 Hystrix,因而不会触发 Hystrix 相干的解决逻辑。

//HystrixTarget.java
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
                    FeignContext context, Target.HardCodedTarget<T> target) {if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { // 没有配置 Hystrix,则走这部分逻辑
        return feign.target(target);
    }
   // 省略....

    return feign.target(target);
}

进入到 Feign.target 办法,代码如下。

//Feign.java
public <T> T target(Target<T> target) {return this.build().newInstance(target);  //target.HardCodedTarget
}

public Feign build() {
    // 这里会构建一个 LoadBalanceClient
    Client client = (Client)Capability.enrich(this.client, this.capabilities);
    Retryer retryer = (Retryer)Capability.enrich(this.retryer, this.capabilities);
    List<RequestInterceptor> requestInterceptors = (List)this.requestInterceptors.stream().map((ri) -> {return (RequestInterceptor)Capability.enrich(ri, this.capabilities);
    }).collect(Collectors.toList());
    //OpenFeign Log 配置
    Logger logger = (Logger)Capability.enrich(this.logger, this.capabilities);
    // 模版解析协定(这个接口十分重要:它决定了哪些注解能够标注在接口 / 接口办法上是无效的,并且提取出无效的信息,组装成为 MethodMetadata 元信息。)Contract contract = (Contract)Capability.enrich(this.contract, this.capabilities);
    // 封装 Request 申请的 连贯超时 = 默认 10s,读取超时 = 默认 60
    Options options = (Options)Capability.enrich(this.options, this.capabilities);
    // 编码器
    Encoder encoder = (Encoder)Capability.enrich(this.encoder, this.capabilities);
    // 解码器
    Decoder decoder = (Decoder)Capability.enrich(this.decoder, this.capabilities);
    
    InvocationHandlerFactory invocationHandlerFactory = (InvocationHandlerFactory)Capability.enrich(this.invocationHandlerFactory, this.capabilities);
    QueryMapEncoder queryMapEncoder = (QueryMapEncoder)Capability.enrich(this.queryMapEncoder, this.capabilities);
    //synchronousMethodHandlerFactory,同步办法调用处理器(很重要,后续要用到)Factory synchronousMethodHandlerFactory = new Factory(client, retryer, requestInterceptors, logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy, this.forceDecoding);
    //ParseHandlersByName 的作用就是咱们传入 Target(封装了咱们的模仿接口,要拜访的域名),返回这个接口下的各个办法,对应的执行 HTTP 申请须要的一系列信息
    ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
    
    return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

build办法中,返回了一个 ReflectiveFeign 的实例对象,先来看 ReflectiveFeign 中的 newInstance 办法。

public <T> T newInstance(Target<T> target) {// 润饰了 @FeignClient 注解的接口办法封装成办法处理器,把指定的 target 进行解析,失去须要解决的办法汇合。Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);    // 定义一个用来保留须要解决的办法的汇合    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();    //JDK8 当前,接口容许默认办法实现,这里是对默认办法进行封装解决。List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();        // 遍历 @FeignClient 接口的所有办法    for (Method method : target.type().getMethods()) {// 如果是 Object 中的办法,则间接跳过        if (method.getDeclaringClass() == Object.class) {continue;} else if (Util.isDefault(method)) {// 如果是默认办法,则把该办法绑定一个 DefaultMethodHandler。DefaultMethodHandler handler = new DefaultMethodHandler(method);            defaultMethodHandlers.add(handler);            methodToHandler.put(method, handler);        } else {// 否则,增加 MethodHandler(SynchronousMethodHandler)。methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));        }    }    // 创立动静代理类。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;}

上述代码,其实也不难理解。

  1. 解析 @FeignClient 接口申明的办法,依据不同办法绑定不同的处理器。

    1. 默认办法,绑定 DefaultMethodHandler
    2. 近程办法,绑定 SynchronousMethodHandler
  2. 应用 JDK 提供的 Proxy 创立动静代理

MethodHandler,会把办法参数、办法返回值、参数汇合、申请类型、申请门路进行解析存储,如下图所示。

FeignClient 接口解析

接口解析也是 Feign 很重要的一个逻辑,它能把接口申明的属性转化为 HTTP 通信的协定参数。

执行逻辑 RerlectiveFeign.newInstance

public <T> T newInstance(Target<T> target) {Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); //here}

targetToHandlersByName.apply(target); 会解析接口办法上的注解,从而解析出办法粒度的特定的配置信息,而后生产一个 SynchronousMethodHandler
而后须要保护一个 <method,MethodHandler> 的 map,放入 InvocationHandler 的实现 FeignInvocationHandler 中。

public Map<String, MethodHandler> apply(Target target) {List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());    Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();    for (MethodMetadata md : metadata) {BuildTemplateByResolvingArgs buildTemplate;        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {buildTemplate =                new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);        } else if (md.bodyIndex() != null) {buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);        } else {buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);        }        if (md.isIgnored()) {result.put(md.configKey(), args -> {throw new IllegalStateException(md.configKey() + "is not a method handled by feign");            });        } else {result.put(md.configKey(),                       factory.create(target, md, buildTemplate, options, decoder, errorDecoder));        }    }    return result;}

为了更好的了解上述逻辑,咱们能够借助上面这个图来了解!

阶段性小结

通过上述过程剖析,被申明为 @FeignClient 注解的类,在被注入时,最终会生成一个动静代理对象 FeignInvocationHandler。

当触发办法调用时,会被 FeignInvocationHandler#invoke 拦挡,FeignClientFactoryBean 在实例化过程中所做的事件如下图所示。

总结来说就几个点:

  1. 解析 Feign 的上下文配置,针对以后的服务实例构建容器上下文并返回 Feign 对象
  2. Feign 依据高低围配置把 log、encode、decoder、等配置项设置到 Feign 对象中
  3. 对指标服务,应用 LoadBalance 以及 Hystrix 进行包装
  4. 通过 Contract 协定,把 FeignClient 接口的申明,解析成 MethodHandler
  5. 遍历 MethodHandler 列表,针对须要近程通信的办法,设置 SynchronousMethodHandler 处理器,用来实现同步近程调用。
  6. 应用 JDK 中的动静代理机制构建动静代理对象。

近程通信实现

在 Spring 启动过程中,把所有的筹备工作准备就绪后,就开始执行近程调用。

在后面的剖析中,咱们晓得 OpenFeign 最终返回的是一个 #ReflectiveFeign.FeignInvocationHandler 的对象。

那么当客户端发动申请时,会进入到 FeignInvocationHandler.invoke 办法中,这个大家都晓得,它是一个动静代理的实现。

//FeignInvocationHandler.java@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {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();    }    return dispatch.get(method).invoke(args);}

接着,在 invoke 办法中,会调用 this.dispatch.get(method)).invoke(args)this.dispatch.get(method) 会返回一个 SynchronousMethodHandler, 进行拦挡解决。

this.dispatch,其实就是在初始化过程中创立的,private final Map<Method, MethodHandler> dispatch;实例。

SynchronousMethodHandler.invoke

这个办法会依据参数生成实现的 RequestTemplate 对象,这个对象是 Http 申请的模版,代码如下,代码的实现如下:

@Override
public Object invoke(Object[] argv) throws Throwable {    //argv,示意调用办法传递的参数。RequestTemplate template = buildTemplateFromArgs.create(argv);  
  Options options = findOptions(argv); // 获取配置项,连贯超时工夫、近程通信数据获取超时工夫  
  Retryer retryer = this.retryer.clone(); // 获取重试策略  
  while (true) {    
    try {return executeAndDecode(template, options);    
    } catch (RetryableException e) {
      try {retryer.continueOrPropagate(e);      
      } catch (RetryableException th) {Throwable cause = th.getCause();        
        if (propagationPolicy == UNWRAP && cause != null) {throw cause;} else {throw th;}      
      }      
      if (logLevel != Logger.Level.NONE) {logger.logRetry(metadata.configKey(), logLevel);      
      }      
      continue;    
    }  
  }
}

上述代码的执行流程中,须要先结构一个 Request 对象,而后调用 client.execute 办法执行网络通信申请,整体实现流程如下。

executeAndDecode

通过上述的代码,咱们曾经将 RequestTemplate 拼装实现,下面的代码中有一个 executeAndDecode() 办法,该办法通过 RequestTemplate 生成 Request 申请对象,而后利用 Http Client 获取 response,来获取响应信息。

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {Request request = targetRequest(template);  // 把 template 转化为申请报文    
  if (logLevel != Logger.Level.NONE) { // 设置日志 level      
    logger.logRequest(metadata.configKey(), logLevel, request); 
  }    
  Response response;   
  long start = System.nanoTime();  
  try {     
    // 发动申请,此时 client 是 LoadBalanceFeignClient,须要先对服务名称进行解析负载    
    response = client.execute(request, options);      // ensure the request is set. TODO: remove in Feign 12          // 获取返回后果   
    response = response.toBuilder().request(request).requestTemplate(template).build();} catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));   
    }     
    throw errorExecuting(request, e);  
  }  
  long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);   
  if (decoder != null) // 如果设置了解码器,这须要对响应数据做解码    
    return decoder.decode(response, metadata.returnType());   
  CompletableFuture<Object> resultFuture = new CompletableFuture<>();  
  asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,        metadata.returnType(),        elapsedTime);  
  try {if (!resultFuture.isDone())  
      throw new IllegalStateException("Response handling not done"); 
    return resultFuture.join();} catch (CompletionException e) {Throwable cause = e.getCause();   
    if (cause != null)    
      throw cause;    
    throw e;  
  } 
}

LoadBalanceClient

@Overridepublic
Response execute(Request request, Request.Options options) throws IOException { 
  try {URI asUri = URI.create(request.url()); // 获取申请 uri,此时的地址还未被解析。String clientName = asUri.getHost(); // 获取 host,实际上就是服务名称  
    URI uriWithoutHost = cleanUrl(request.url(), clientName);    
    FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost);        // 加载客户端的配置信息     
    IClientConfig requestConfig = getClientConfig(options, clientName);       // 创立负载平衡客户端(FeignLoadBalancer),执行申请   
    return lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();} catch (ClientException e) {IOException io = findIOException(e); 
    if (io != null) {throw io;}     
    throw new RuntimeException(e); 
  }
}

从下面的代码能够看到,lbClient(clientName) 创立了一个负载平衡的客户端,它实际上就是生成的如下所述的类:

public class FeignLoadBalancer extends        AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {

整体总结

OpenFeign 的整体通信原理解析,如下图所示。

版权申明:本博客所有文章除特地申明外,均采纳 CC BY-NC-SA 4.0 许可协定。转载请注明来自 Mic 带你学架构
如果本篇文章对您有帮忙,还请帮忙点个关注和赞,您的保持是我一直创作的能源。欢送关注同名微信公众号获取更多技术干货!

正文完
 0