Understanding Feign Source for Spring cloud Source Text

Keywords: Programming Spring encoding Dubbo SpringBoot

Introduction to Feign

In the previous article, we analyzed Eureka's registration, renewal, service culling, service self-protection and other mechanisms. https://blog.csdn.net/lgq2626/article/details/80288992 .This article analyzes the feign of SpringCloud.Calls between SpringCloud micro-service items are made through httpress requests, which were previously handled by tools such as HttpClient. Spring handles these requests and encapsulates them as declarative web clients, making it easier to write web clients, feign supports pluggable encoders and decoders, and Spring adds them when usedWith the @requestMapping process, SpringCloud also integrates a registry (eureka) and client load balancing (ribbon) with feign, enabling us to have a client load balanced web request client.

Configuration and use of Feign in projects

When using feign in Springcloud, you need to include a @EnableFeignClients annotation in the configuration class.The code is as follows:

@SpringBootApplication//springboot startup class
@EnableFeignClients//Turn on eureka scan
@EnableDiscoveryClient//Open eureka client
public class Application {
    public static void main( String[] args ) throws ClassNotFoundException {
        SpringApplication.run(Application.class, args);
    }
}

Configure feign calling client

@FeignClient(value = "xxx-server",configuration = FeignConfiguration.class)
public interface ConsumerSmsService extends SMSService{
    @RequestMapping(value = "/sms/smsMessage", method = RequestMethod.POST)
    RespSMSDto sendSms(ReqSMSDto smsReqDto);
}

With the above configuration, you can inject the container call interface directly into the project.

Feign Source Analysis

In the @EnableFeignClients tag, import a FeignClientsRegistrar class. When did this FeignClientsRegistrar#registerBeanDefinitions() be called?Following Spring's source code, anyone who has looked at it will see the AbstractApplicationContext#refresh() method directly and tidy up the code as a whole:

@Override
public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
				// Scan the java files in this project, encapsulate the bean object as a BeanDefinitiaon object, and call the DefaultListableBeanFactory#registerBeanDefinition() method to place the beanName in the List DefaultListableBeanFactory's List<String> BeanDefinitionNames
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            postProcessBeanFactory(beanFactory);

            // Call the registerBeanDefinitions() method here to the FeignClientsRegistrar object
            invokeBeanFactoryPostProcessors(beanFactory);

            //Find all the methods that implement the BeanPostProcessor interface from the beanDefinitionNames inside the DefaultListableBeanFactory and place them in the list if there is a sort
            registerBeanPostProcessors(beanFactory);

            //Internationalization of Spring
            initMessageSource();

            // 
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            onRefresh();

            // 
            registerListeners();

            // IOC, ID processing for Spring.Spring's AOP.Transactions call BeanPostProcessor#postProcessBeforeInitialization() and postProcessBeforeInitialization() methods after the IOC is completed, and that's where AOP handles it
            finishBeanFactoryInitialization(beanFactory);

            // After execution, call the onRefresh() method of the class that implements all LifecycleProcessor interfaces, and call all events that observe the ApplicationEvent interface (observer mode)
            finishRefresh();
        }

        catch (BeansException ex) {

            // Find all the methods that implement the DisposableBean interface and call the destroy() method, which is the destruction of the bean
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            throw ex;
        }

        finally {
            resetCommonCaches();
        }
    }
}

According to the code sorted above, the FeignClientsRegistrar#registerBeanDefinitions() method is when only one beanname is placed after the beans have been scanned.This is called when IOC registration is not done. This is Spring's dynamically extended Bean, where all methods that implement the BeanDefinitionRegistryPostProcessor interface also call the postProcessBeanDefinitionRegistry() method.That's what Spring is all about.Returning to the topic below, analyze the FeignClientsRegistrar#registerBeanDefinitions() method:

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
        BeanDefinitionRegistry registry) {
    registerDefaultConfiguration(metadata, registry);//Scan the information configured in the EnableFeignClients tag and register it with the beanDefinitionNames.
    registerFeignClients(metadata, registry);
}

public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
        //Omit Code...Find all the FeignClient annotated classes under the package based on the basePackages configured by EnableFeignClients, and Spring's Commponet does the same
		for (String basePackage : basePackages) {
				Set<BeanDefinition> 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);
               /**
                 * Key: Feign subcontainer concept:
                 * When injecting the FeignAutoConfiguration class, a FeignContext object is injected, which is a child container of Feign.
                 * The List <FeignClientSpecification>object is loaded here. The essence of the FeignClientSpecification object is that the name configured on @feignClient is key and the value is the value of the configuration object.
                 * For example, @FeignClient(url="https://api.weixin.qq.com",name="${usercenter.name}", configuration = UserCenterFeignConfiguration.class, primary= false) is configured like feignclient.
                 * A FeignClientSpecification{name='sms-server', configuration=[class com.jfbank.sms.configuration.FeignConfiguration]} appears in the FeignContext.
                 *  This is a key place, mainly because custom classes are used for later feign client encoding and decoding
                 */
                //This method is to construct a FeignContext subcontainer by inserting a FeignClientSpecification object into the ioc container.
                registerClientConfiguration(registry, name,
                        attributes.get("configuration"));       
                //Focus on this
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);//Generate a BeanDefinition object on a FeignClientFactoryBean object
    ...Read Configuration
    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 });
    //Register objects in beanDefinitionNames
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);//
}

(All students who have read the Dubbo source code know that when the reference tag is parsed in the Dubbo NamespaceHandler, a ReferenceBean object is passed in, and all the properties configured in the xml are stuffed into this object, as well as into the beanDefinitionNames. Then they find that both the ReferenceBean class and the FeignClientFactoryBean implement the interface of FactoryBean with getObject inside() and getObjectType() methods.When the interface calls this feign client, the FeignClientFactoryBean is read from the IOC and the getObject method is called.The following is an analysis of the getObject method:

 @Override
    public Object getObject() throws Exception {
        FeignContext context = applicationContext.getBean(FeignContext.class);
        //Get custom classes such as encoders, decoders, etc. from the subcontainers above, and encapsulate a Feign.Builder class
        Feign.Builder builder = feign(context);
    if (!StringUtils.hasText(this.url)) {//When @FeignClient has no url configured
        String url;
        if (!this.name.startsWith("http")) {
            url = "http://" + this.name;
        }
        else {
            url = this.name;
        }
        url += cleanPath();
        return loadBalance(builder, context, new HardCodedTarget<>(this.type,
                this.name, url));//Integrated ribbon client load balancing, next analysis
    }
    //When @FeignClient is configured with 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 lod balancing because we have a url,
            // but ribbon is on the classpath, so unwrap
            client = ((LoadBalancerFeignClient)client).getDelegate();
        }
        builder.client(client);
    }
    Targeter targeter = get(context, Targeter.class);
    return targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url));
}

First, look at the feignclient parsing with the URL configured, specifying the url, and follow the code to the Feign.Builder#target() method:

public <T> T target(Target<T> target) {
  return build().newInstance(target);
}
    
public Feign build() {
  SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
      new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                                           logLevel, decode404);
  ParseHandlersByName handlersByName =
      new ParseHandlersByName(contract, options, encoder, decoder,
                              errorDecoder, synchronousMethodHandlerFactory);
  return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}

Look directly at the ReflectiveFeign#newInstance() method:

//ReflectiveFeign#newInstance()
public <T> T newInstance(Target<T> target) {
	//The handler class of the dynamic proxy is currently passing in the ParseHandlersByName class, so here's a look at ParseHandlersByName#apply() and look directly at the next method
	Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
	Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
	List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
	for (Method method : target.type().getMethods()) {
    if (method.getDeclaringClass() == Object.class) {
      continue;
    } else if(Util.isDefault(method)) {//The default method comes here, such as toString(), hashCode(), and so on.
      DefaultMethodHandler handler = new DefaultMethodHandler(method);
      defaultMethodHandlers.add(handler);
      methodToHandler.put(method, handler);
    } else {//This is the assembled call class, and the interest-bearing handler analyzed above is SynchronousMethodHandler#invoke()
      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);//jdk dynamic proxy

  for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    defaultMethodHandler.bindTo(proxy);
  }
  return proxy;
}

//ParseHandlersByName#apply class, building handler for dynamic proxy
public Map<String, MethodHandler> apply(Target key) {
    List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.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);//Resolving parameters through a custom encoder
      } else if (md.bodyIndex() != null) {
        buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder);//Resolving parameters through a custom encoder
      } else {
        buildTemplate = new BuildTemplateByResolvingArgs(md);
      }
      //Create handler, look at Factory#create() method, next method
      result.put(md.configKey(),factory.create(key, md, buildTemplate, options, decoder, errorDecoder));
    }
    return result;
  }
//Factory#create(), build a SynchronousMethodHandler to process the request, call the invoke method
public MethodHandler create(Target<?> target, MethodMetadata md,
        RequestTemplate.Factory buildTemplateFromArgs,
        Options options, Decoder decoder, ErrorDecoder errorDecoder) {
    return new SynchronousMethodHandler(target, client, retryer, requestInterceptors, logger,
                  logLevel, md, buildTemplateFromArgs, options, decoder,
                  errorDecoder, decode404);
}

//SynchronousMethodHandler#invoke() method: the method actually called
//@Override
public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);//Build requestTemplate object
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template);//Without analysis, execute the method and decode the after-meal return value
      } catch (RetryableException e) {
        retryer.continueOrPropagate(e);
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

Feign Source Summary

From reading annotations to injecting into IOC containers, to encoding parameters, making requests, and decoding results, the whole encapsulation process facilitates our development. This article only analyses how feign parses with url parameters and integrates eureka with ribbon. The analysis was done at https://blog.csdn.net/lgq2626/article/details/80481514.The following flowchart summarizes the following process:

Reference Address

If you like my article, you can focus on your personal subscription number.Welcome to leave a message and exchange at any time.If you want to join the WeChat group and discuss it together, add lastpass4u, the administrator's culture assistant, who will pull you into the group.

Posted by Mount Tropolis on Fri, 03 Apr 2020 23:55:28 -0700