preface

In the last part, Feign’s core implementation principle was introduced, and its integration principle with Spring Cloud was also introduced at the end of this article. Spring has strong expansibility, and opens up some common solutions to developers through starter. After introducing the officially provided starter, you usually only need to add some annotations to use the function (usually @enablexxx). Here’s a look at how Spring Cloud integrates Feign.

Analysis of integration Principle

In Spring everything works around beans, and all beans are generated based on BeanDefinition, which is the cornerstone of the Spring empire, The key to this integration is how to generate Feign’s corresponding BeanDefinition.

To analyze the integration principle, where do we start? If you saw the previous article, the second step in introducing the example of using Spring Cloud in conjunction with the project is to add the @enableFeignClients annotation to the XXXApplication. We can use this as a starting point. Step by step, drill down into how this works (a significant number of starter classes typically add annotations to enable related functionality).

Enter the @enableFeignClients annotation, which reads as follows:

The @import annotation introduces the FeignClientsRegistrar class via @import, in addition to defining several parameters (basePackages, defaultConfiguration, Clients, etc.). In general, @import annotations have the following functions (see the official Java Doc for details) :

  • Declare a Bean
  • Import the Configuration class for the @Configuration annotation
  • Import the implementation class for ImportSelector
  • Import ImportBeanDefinitionRegistrar implementation class (here using this feature)

The integration registrar is via the FeignClientsRegistrar class.

By source knowable FeignClientsRegistrar ImportBeanDefinitionRegistrar interface, the interface from the name isn’t hard to see its main function is to need to initialize BeanDefinition injected into the container, Both methods are used to inject a given BeanDefinition, a customizable beanName (by implementing the BeanNameGenerator interface to customize the logic for generating beanName), The other uses the default rule to generate beanName (lowercase for class names). The source code of the interface is as follows:

For those of you who know something about Spring, it initializes the class from the BeanDefinition property information during container startup and injects it into the container. So the end-point of FeignClientsRegistrar is to inject the generated proxy classes into the Spring container.

The FeignClientsRegistrar class looks pretty extensive, but the end point is to look at generating BeanDefinition, Through the source code can be found that the realized ImportBeanDefinitionRegistrar interface, In addition, the registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry) method was rewritten, and some beandefinitions were generated and registered in this method. The source code is as follows:

The whole process is mainly divided into the following two steps:

  1. Create a BeanDefinition object for the global defaultConfiguration of @enablefeignclients (annotated defaultConfiguration property) and inject it into the container (corresponding to step 1 in the figure above)
  2. Create a BeanDefinition object for the class labeled @FeignClient and inject it into the container (corresponding to step 2 in the figure above).

Method respectively under the deep source implementation to see its concrete realization principle, first take a look at the first step of method registerDefaultConfiguration (AnnotationMetadata, BeanDefinitionRegistry), the source code is as follows:

As you can see, we are simply getting the value of the @enablefeignclients defaultConfiguration property, defaultConfiguration, The realization of the function of the final to registerClientConfiguration (BeanDefinitionRegistry, Object, Object) method to complete, continue to follow up further the method, its source code is as follows:

As you can see, the BeanClazz for the global default configuration is FeignClientSpecification, and then here I set the global default configuration configuration to the input parameter to the BeanDefinition constructor, This parameter is then passed in when the constructor is instantiated. Now that the @enablefeignclients global defaultConfiguration (the annotation’s defaultConfiguration property) has been created into a BeanDefinition object and injected into the container, the first step is relatively simple.

Let’s look at how step 2 creates a BeanDefinition object for the class labeled @FeignClient and injects it into the container. For registerFeignClients(BeanDefinitionRegistry), there is more code for method implementation, so the use of screenshots will be scattered. So do it by Posting the source code and adding necessary comments where appropriate:

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // Finally get a collection of @feignClient annotation classes
    LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
    // Get the attribute map of the @enableFeignClients annotation
    Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
    // Get the Clients attribute of the @enableFeignClients annotation
    finalClass<? >[] clients = attrs ==null ? null: (Class<? >[]) attrs.get("clients");
    if (clients == null || clients.length == 0) {  
        // If the @enableFeignClients annotation does not specify the clients attribute, the scan is added (filter: classes marked with @feignClient).
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
        Set<String> basePackages = getBasePackages(metadata);
        for(String basePackage : basePackages) { candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); }}else {
        // If the @enableFeignClients annotation already specifies a clients attribute, add it to it without scanning.
        for(Class<? > clazz : clients) { candidateComponents.add(newAnnotatedGenericBeanDefinition(clazz)); }}// Iterate over the final collection of @FeignClient annotation classes
    for (BeanDefinition candidateComponent : candidateComponents) {
        if (candidateComponent instanceof AnnotatedBeanDefinition) {
            // verify annotated class is an interface
            // Verify that the annotated class must be an interface, or throw an exception if it is not.
            AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
            AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
            Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
            // Get the @feignClient attribute value
            Map<String, Object> attributes = annotationMetadata
                    .getAnnotationAttributes(FeignClient.class.getCanonicalName());
            GetClientName (Map
      
       ) getClientName(Map
       ,>
      ,>
            String name = getClientName(attributes);
            // Inject the @feignClient annotated configuration object into the container as we did at the end of the first step above
            registerClientConfiguration(registry, name, attributes.get("configuration"));
            // inject the @feignclient object, which can be imported directly from other classes via @autowired (e.g. XXXService)registerFeignClient(registry, annotationMetadata, attributes); }}}Copy the code

RegisterFeignClient (BeanDefinitionRegistry, AnnotationMetadata, Map<String, Object>) injected @feignClient Object, further into the method, source code as follows:

Method implementation is long, and the ultimate goal is to construct a BeanDefinition object, Then through BeanDefinitionReaderUtils. RegisterBeanDefinition (BeanDefinitionHolder BeanDefinitionRegistry) injection into the container. The key step is to take the information from the @FeignClient annotation and set it in BeanDefinitionBuilder. The class registered in BeanDefinitionBuilder is FeignClientFactoryBean, The function of this class, as its name implies, is to create the FeignClient Bean, which Spring then generates objects from and injects into the container.

To be clear, what actually ends up being injected into the container is the class FeignClientFactoryBean from which Spring generates instance objects when the class is initialized. Is called FeignClientFactoryBean. GetObject () method, the resulting object is we actually use the proxy objects. FeignClientFactoryBean getObject() ¶

GetTarget () : getTarget() : getTarget() : getTarget() : getTarget() : getTarget() : getTarget() : getTarget() : getTarget()

/ * * *@param <T> the target type of the Feign client
  * @return a {@link Feign} client created with the specified data and the context
  * information
  */
<T> T getTarget(a) {
    // Get the FeignContext Bean from the Spring containerFeignContext context = beanFactory ! =null ? beanFactory.getBean(FeignContext.class)
            : applicationContext.getBean(FeignContext.class);
    // Build feign.builder from the obtained FeignContext
    Feign.Builder builder = feign(context);

    // Annotation @feignClient does not specify a URL attribute
    if(! StringUtils.hasText(url)) {// The url attribute is a fixed access to an instance address. If no protocol is specified, the HTTP request protocol is concatenated
        if(! name.startsWith("http")) {
            url = "http://" + name;
        }
        else {
            url = name;
        }
        // Format the URL
        url += cleanPath();
        Like our previous proxy, the @feignClient annotation returns a client object with load balancing functionality without specifying a URL attribute
        return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
    }
    // Annotation @feignClient has specified the URL attribute
    if(StringUtils.hasText(url) && ! url.startsWith("http")) {
        url = "http://" + url;
    }
    String url = this.url + cleanPath();
    // Get a client
    Client client = getOptional(context, Client.class);
    if(client ! =null) {
        if (client instanceof FeignBlockingLoadBalancerClient) {
            // not load balancing because we have a url,
            // but Spring Cloud LoadBalancer is on the classpath, so unwrap
            // There is no load here because we have specified the URL
            client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
        }
        builder.client(client);
    }
    // The generated proxy is finally injected into the Spring container, just like our previous proxy
    Targeter targeter = get(context, Targeter.class);
    return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}
Copy the code

FeignClientFactoryBean inherits FactoryBean. Its method FactoryBean. GetObject returns Feign’s proxy object, which is injected into the Spring container. We can inject and use it directly via @autoWired. You can also see that the above code branches all end up in the following code:

Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
Copy the code

If you dig into the targeter.target source code, you can see that what is actually created is a proxy object, that is, a proxy object is created for each @FeignClient when the container is started. This completes the core implementation of the Spring Cloud and Feign integration principles.

conclusion

This article mainly introduces the principle of Spring Cloud to integrate Feign. From the introduction above, you already know that Srpring creates a proxy object for our annotated @feignClient interface, and with this proxy object we can do enhancements (e.g Pre enhancement, post enhancement), and do you know how it works? Those who are interested can look through the source code again to find the answer (note: the enhanced logic is in the InvocationHandler). Feign also works with the Ribbon and Hystrix, so you can download the source code for yourself.