6

OpenFeign is a remote client request proxy. Its basic function is to enable developers to implement remote calls in an interface-oriented manner, thereby shielding the complexity of underlying communication. Its specific principle is shown in the following figure.

image-20211215192443739

In today's content, we need to analyze the working principle and source code of OpenFeign in detail, and we continue to return to this code.

@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;
    }
}

From this code, first lead to thinking about the implementation of OpenFeign functions.

  1. How is the interface declaring the @FeignClient annotation parsed and injected?
  2. Through @Autowired dependency injection, what kind of instance is injected?
  3. After the interface declared based on FeignClient is resolved, how to store it?
  4. What does the overall workflow look like when a method call is made?
  5. How does OpenFeign integrate Ribbon for load balancing analysis?

With these questions, start to analyze the core source code of OpenFeign item by item

OpenFeign annotation scanning and parsing

Think, an interface declared with @FeignClient annotation uses @Autowired for dependency injection, and finally this interface can be injected into the instance normally.

From this result, two conclusions can be drawn

  1. The interface declared by @FeignClient will be resolved when the Spring container starts.
  2. Since the interface is loaded by the Spring container, and the interface has no implementation class, a dynamic proxy class will be generated when the Spring container resolves.

EnableFeignClient

When is the @FeignClient annotation parsed? Based on all the knowledge we have accumulated before, nothing more than the following

  • ImportSelector, bulk import beans
  • ImportBeanDefinitionRegistrar, import bean declaration and register
  • BeanFactoryPostProcessor , a pre- and post-processor where a bean is loaded

Among these options, it seems that ImportBeanDefinitionRegistrar is more suitable, because the first is to batch import a string collection of a bean, which is not suitable for dynamic bean declaration. And BeanFactoryPostProcessor is a handler called before and after bean initialization.

In our FeignClient declaration, there are no Spring-related annotations, so naturally it will not be loaded and triggered by the Spring container.

So where is @FeignClient declared to be scanned?

When integrating FeignClient, we declared an annotation @EnableFeignClients(basePackages = "com.gupaoedu.ms.api") in the main method of SpringBoot. This annotation requires a specified package name to be filled in.

Well, seeing this, you can basically guess that this annotation must have a great relationship with the analysis of the @FeignClient annotation.

The following code is the declaration of @EnableFeignClients annotation, and I saw a very familiar face FeignClientsRegistrar .

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

FeignClientsRegistrar

FeignClientRegistrar, the main function is to scan and inject into the IOC container for the interface that declares the @FeignClient annotation.

class FeignClientsRegistrar
      implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    
}

Sure enough, this class implements the ImportBeanDefinitionRegistrar interface

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

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

This interface has two overloaded methods for implementing Bean declaration and registration.

Simply show you the role of ImportBeanDefinitionRegistrar.

In the gpmall-portal directory of the com.gupaoedu project, create them separately

  1. HelloService.java
  2. GpImportBeanDefinitionRegistrar.java
  3. EnableGpRegistrar.java
  4. TestMain
  1. Define a class HelloService that needs to be loaded into the IOC container
public class HelloService {
}
  1. Define a Registrar implementation, define a bean, and load it into the IOC container
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. define an annotation class
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {
}
  1. write a test class
@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. Through the result demonstration, it can be found that the bean HelloService has been loaded into the IOC container.

This is the function implementation of dynamic loading, which has a lot more flexibility than @Configuration configuration injection. ok, go back to the analysis of FeignClient.

FeignClientsRegistrar.registerBeanDefinitions

  • Inside the registerDefaultConfiguration method, check whether there is @EnableFeignClients from the SpringBoot startup class. If there is this annotation, then complete the registration of some configuration content related to the Feign framework.
  • Inside the registerFeignClients method, scan the class decorated with @FeignClient from classpath , parse the content of the class into BeanDefinition , and finally add the parsed FeignClient BeanDeifinition to the BeanDefinitionReaderUtils.resgisterBeanDefinition by calling spring in the Spring framework
//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);
}
What needs to be analyzed here is the registerFeignClients method. This method mainly scans all the @FeignClient annotations in the classpath, and then performs dynamic bean injection. It will eventually call registerFeignClient method.
public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
    registerFeignClient(registry, annotationMetadata, attributes);
}

FeignClientsRegistrar.registerFeignClients

The definition of the registerFeignClients method is as follows.

//# 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);
      }
   }
}

Registration of FeignClient Bean

This method is to register the FeignClient interface with the Spring IOC container,

FeignClient is an interface, so what is the object injected into the IOC container here?
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容器。
}

In summary, the code analysis, in fact, the implementation logic is very simple.

  1. Create a BeanDefinitionBuilder.
  2. Create a factory bean and set the properties resolved from the @FeignClient annotation to this FactoryBean
  3. Call registerBeanDefinition to register in the IOC container

About FactoryBean

In the above logic, what we need to focus on is FactoryBean, which you may have less exposure to.

First of all, it should be noted that FactoryBean and BeanFactory are different. FactoryBean is a factory Bean that can generate a Bean instance of a certain type. Its biggest function is to allow us to customize the Bean creation process.

However, BeanFactory is a basic class in the Spring container and is also a very important class. In the BeanFactory, beans in the Spring container can be created and managed.

The following code is the definition of the FactoryBean interface.

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

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}

From the above code, we found that three important characteristics of a Spring Bean are defined in FactoryBean: whether it is a singleton, Bean type, and Bean instance. This should also be our most intuitive feeling about a Bean in Spring.

FactoryBean custom use

Let's simulate the @FeignClient parsing and factory bean construction process.

  1. First define an interface, which can be analogous to the FeignClient we described above.
public interface IHelloService {

    String say();
}
  1. Next, define a factory bean, which mainly generates dynamic proxy for 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. Dynamically inject Bean instances by implementing the ImportBeanDefinitionRegistrar interface
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. Declare an annotation to represent the injected import of a dynamic bean.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {
}
  1. Write a test class to test the dynamic injection of the IHelloService interface
@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. Running the above test method, you can see that the interface IHelloService is dynamically proxied and injected into the IOC container.

FeignClientFactoryBean

As can be seen from the above case, when the Spring IOC container injects FactoryBean, it will call the getObject() method of FactoryBean to obtain an instance of the bean. So we can follow this idea to analyze FeignClientFactoryBean

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

The implementation code for constructing the object bean is as follows. The implementation of this code is long. Let's look at it in several steps.

Construction of Feign context

Let's first look at the construction logic of the context, the code part is as follows.

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

Two key object descriptions:

  1. FeignContext is the only context in the world. It inherits NamedContextFactory. It is used to uniformly maintain the isolated context of each feign client in feign. The registration of FeignContext to the container is done on FeignAutoConfiguration. The code is as follows!
//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;
}

When initializing FeignContext, configurations will be put into FeignContext. configuration represents all @FeignClients currently being scanned.

  1. Feign.Builder is used to build Feign objects, based on the builder to realize the construction of context information, the code is as follows.
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;
}

As can be seen from the code, the feign method mainly generates Feign context information for different service providers, such as logger , encoder , decoder , etc. Therefore, from this analysis process, it is not difficult for us to guess its principle structure, as shown in the following figure

image-20211215192527913

The implementation of parent-child container isolation is as follows. When the get method is called, the instance object of the specified type will be obtained from context .

//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;
}

Next, call the NamedContextFactory method in 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 method obtains the context object from the contexts container according to name , and if not, calls createContext to create it.

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);
}

Generate dynamic proxy

In the second part, if 06205e65711a4f is not configured in the url annotation, that is, the absolute request path is not taken, the following logic is executed.

Since we are using name in the @FeignClient annotation, we need to execute the branch logic of the load balancing strategy.

<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));
   }
    //省略....
}

The code implementation of the loadBalance method is as follows, of which Client is implemented when Spring Boot is automatically assembling. If other http protocol frameworks are replaced, the client corresponds to the configured protocol 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?");
}

The code of HystrixTarget.target is as follows. We do not integrate Hystrix, so the processing logic related to Hystrix will not be triggered.

//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);
}

Enter the Feign.target method, the code is as follows.

//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);
}

In the build method, an instance object of ReflectiveFeign is returned. Let's first look at the ReflectiveFeign method in 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;}

The above code is actually not difficult to understand.

  1. Parse the methods declared by the @FeignClient interface, and bind different processors according to different methods.

    1. Default method, bind DefaultMethodHandler
    2. Remote method, bind SynchronousMethodHandler
  2. Create a dynamic proxy using the Proxy provided by the JDK

MethodHandler will parse and store method parameters, method return values, parameter sets, request types, and request paths, as shown in the following figure.

image-20211123152837678

FeignClient interface analysis

Interface parsing is also a very important logic in Feign, which can convert the properties declared by the interface into the protocol parameters of HTTP communication.

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

targetToHandlersByName.apply(target); will parse the annotations on the interface method, so as to parse out the specific configuration information of the method granularity, and then produce a SynchronousMethodHandler
Then you need to maintain a map of <method, MethodHandler> and put it into the FeignInvocationHandler implementation of InvocationHandler.

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;}

In order to better understand the above logic, we can use the following picture to understand!

Periodic summary

Through the above process analysis, the class declared as @FeignClient annotation will eventually generate a dynamic proxy object FeignInvocationHandler when it is injected.

When the method call is triggered, it will be intercepted by FeignInvocationHandler#invoke. What FeignClientFactoryBean does during the instantiation process is shown in the following figure.

image-20211215192548769

To sum up a few points:

  1. Parse Feign's context configuration, build a container context for the current service instance and return the Feign object
  2. Feign sets the log, encode, decoder, and other configuration items to the Feign object according to the upper and lower surrounding configurations
  3. For the target service, use LoadBalance and Hystrix for packaging
  4. Through the Contract protocol, the declaration of the FeignClient interface is parsed into a MethodHandler
  5. Traverse the MethodHandler list, and set the SynchronousMethodHandler handler for methods that require remote communication to implement synchronous remote calls.
  6. Build dynamic proxy objects using the dynamic proxy mechanism in the JDK.

Remote communication implementation

In the Spring startup process, after all the preparations are ready, the remote call is started.

In the previous analysis, we know that OpenFeign finally returns an object of #ReflectiveFeign.FeignInvocationHandler.

Then when the client initiates a request, it will enter the FeignInvocationHandler.invoke method, which we all know is an implementation of a dynamic proxy.

//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);}

Next, in the invoke method, this.dispatch.get(method)).invoke(args) is called. this.dispatch.get(method) will return a SynchronousMethodHandler for interception processing.

this.dispatch, which is actually created during the initialization process, is an instance of private final Map<Method, MethodHandler> dispatch; .

SynchronousMethodHandler.invoke

This method will generate the completed RequestTemplate object according to the parameters, which is the template of the Http request. The code is as follows, and the implementation of the code is as follows:

@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;    
    }  
  }
}

In the execution process of the above code, a Request object needs to be constructed first, and then the client.execute method is called to execute the network communication request. The overall implementation process is as follows.

image-20211215192557345

executeAndDecode

After the above code, we have assembled the RequestTemplate. There is a executeAndDecode() method in the above code. This method generates the Request request object through the RequestTemplate, and then uses the Http Client to obtain the response to obtain the response information.

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); 
  }
}

As you can see from the above code, lbClient(clientName) creates a load balancing client, which is actually the generated class as described below:

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

Overall summary

Analysis of the overall communication principle of OpenFeign, as shown in the following figure.

image-20211215192455398

Copyright notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated. Reprint please indicate from Mic takes you to learn architecture!
If this article is helpful to you, please help to follow and like, your persistence is the driving force for my continuous creation. Welcome to follow the WeChat public account of the same name to get more technical dry goods!

跟着Mic学架构
810 声望1.1k 粉丝

《Spring Cloud Alibaba 微服务原理与实战》、《Java并发编程深度理解及实战》作者。 咕泡教育联合创始人,12年开发架构经验,对分布式微服务、高并发领域有非常丰富的实战经验。