preface

In the last part, the core implementation principle of Feign was introduced. At the end of this article, the integration principle of Feign and Spring Cloud was also introduced. Spring has strong expansibility and will open some common solutions to developers by means of starter. After introducing the official starter, it is usually just a matter of adding a few comments (usually @enablexxx) to enable the relevant functionality. Here’s a look at how Spring Cloud integrates Feign.

Analysis of Integration Principle

Everything in Spring revolves around beans, and all beans are generated based on BeanDefinition, which is the cornerstone of the entire Spring empire. The key to this integration is how to generate the BeanDefinition for FeIGN.

To analyze its integration principle, where should we start from first? If you have seen the previous article, the next step is to add the @EnableFeignClients annotation to the xxxApplication of the project. We can use this annotation as a starting point. Take a step-by-step look at how it works (usually a good part of the starter is to add annotations to the startup class that enable the relevant functionality).

Enter the @EnableFeignClients annotation with the source code as follows:

As you can see from the source code of the annotation, in addition to defining a few parameters (BasePackages, DefaultConfiguration, Clients, and so on), the annotation also introduces the FeignClientsRegistrar class via @Import. 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 of ImportSelector
  • Import ImportBeanDefinitionRegistrar implementation class (here using this feature)

The main process of integration implementation is in the FeignClientsRegistrar class. Let’s dig into the source code of 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 method functions are used to inject a given BeanDefinition, a customizable BeanName (by implementing the BeanNameGenerator interface to customise the logic that generates the BeanName), The other uses the default rules to generate the beanName (lowercase format for the first letter of the class name). The source code of the interface is as follows:

If you know anything about Spring, you will know that Spring initializes the class and injects it into the container during container startup based on BeanDefinition property information. So the ultimate goal here with FeignClientsRegistrar is to inject the generated proxy class into the Spring container. While the FeignClientsRegistrar class looks like it has a lot of source code, in terms of its end goal, we’re mainly looking at how to generate BeanDefinition, Through the source code can be found that the realized ImportBeanDefinitionRegistrar interface, I rewrote the registerBeanDefinitions(annotationMetadata, beanDefinitionRegistry) method to generate and register some of the BeanDefinitions. The source 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 (the DefaultConfiguration property of the annotation) and inject it into the container (corresponding to step ① in the figure above).
  2. Create a BeanDefinition object for the @FeignClient class and inject it into the container (corresponding to step ② 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:

You can see that this is just getting the value of the defaultConfiguration property defaultConfiguration for the annotation @EnableFeignClients. 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 of the global default configuration is the FeignClientSpecification, and here we set the global default configuration as an input parameter to the BeanDefinition constructor. This parameter is then passed in when the call constructor is instantiated. You’ve created the BeanDefinition object for the global defaultConfiguration of @EnableFeignClients (the DefaultConfiguration property of the annotation) and injected it into the container. This is the first step, but it’s relatively simple overall.

Now let’s look at the second step of creating a BeanDefinition object for the class labeled @FeignClient and injecting it into the container. In the implementation of the second step registerFeignClients(annotationMetadata, beanDefinitionRegistry), the use of screenshots is more fragmented due to the more code of the method implementation. So do it by Posting the source code and adding the necessary comments in the relevant places:

public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry) {// Finally get the collection LinkedHashSet with @FeignClient annotation class <BeanDefinition> CandidateComponents = new  LinkedHashSet<>(); // Get the @EnableFeignClients attribute map map <String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName()); // get @EnableFeignClients final Class<? >[] clients = attrs == null ? null : (Class<? >[]) attrs.get("clients"); If (clients = = null | | clients. The length = = 0) {/ / if @ EnableFeignClients annotation is not specified clients attributes are scanning add (scanning filter conditions as follows: Mark @ FeignClient class) 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 has already specified the Clients attribute, add it without scanning it. For (Class<? > clazz : clients) { candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz)); }} // For (BeanDefinition CandidateComponent: BeanDefinition CandidateComponent) candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { // verify annotated class is an // Verify that the annotated class must be an interface, or throw an exception if it is not an interface. 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()); // Get the value of clientName, GetClientName (Map<String, Object>) method String Name = getClientName(Attributes); // same as the last method called in the first step above, Injection @ FeignClient annotation configuration object into the container registerClientConfiguration (registry, the name, the attributes. The get (" configuration ")); // Inject @FeignClient object, This object can be introduced directly in other classes by @Autowired (e.g. XXXService) RegisterFeignClient (Registry, AnnotationMetadata, Attributes); }}}

You can see that the last one is registerFeignClient(beanDefinitionRegistry, annotationMetadata, Map<String, Object>) into the @FeignClient Object, and continue to drill into the method. The source code is as follows:

Method implementation is long, and the ultimate goal is to construct the BeanDefinition object, Then through BeanDefinitionReaderUtils. RegisterBeanDefinition (BeanDefinitionHolder BeanDefinitionRegistry) injection into the container.

The key step is to get the information from the @FeignClient annotation and set it in the BeanDefinitionBuilder, where the registered class is the FeignClientFactoryBean, The function of this class, as its name suggests, is to create a Bean for FeignClient. Spring then generates objects from FeignClientFactoryBean and injects them into the container.

One thing to be clear is that what is actually injected into the container is the class FeignClientFactoryBean that Spring will use to generate instance objects from when the class is initialized. Is called FeignClientFactoryBean. GetObject () method, the resulting object is we actually use the proxy objects. Let’s go to the getObject() method of class FeignClientFactoryBean. The source code is as follows:

As you can see, this method is a direct call to another method in the class called getTarget(). I’m going to follow up on this method. Since there is a lot of code to implement this method, the use of screenshots is quite scattered, so I’m going to post the source code and add the necessary comments in the relevant place:

/** * @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() {// Get the FeignContext Bean from the Spring container FeignContext = beanFactory ! = null ? beanFactory.getBean(FeignContext.class) : applicationContext.getBean(FeignContext.class); // Construct Feign.Builder Feign.Builder Builder = Feign (context); // annotation @feignClient does not specify the URL attribute if (! StringUtils.hastext (url)) {// The url property is the address of a fixed access instance, if no protocol is specified then the HTTP request protocol is concatenated if (! name.startsWith("http")) { url = "http://" + name; } else { url = name; } // Format URL URL += cleanPath(); // Generate the proxy just like we did before, Return (T) loadBalance(builder, context, new HardCodedTarget<>(type,) name, url)); } // annotation @FeignClient has specified the URL property if (StringUtils.hastext (URL) &&! url.startsWith("http")) { url = "http://" + url; } String url = this.url + cleanPath(); // Get a 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 is because we have to specify the url the client = ((FeignBlockingLoadBalancerClient) client). GetDelegate (); } builder.client(client); } // The generated agent is injected into the Spring container just like the one we used before. return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url)); }

FeignClientFactoryBean inherits FactoryBean, and its method FactoryBean.getObject returns the Feign proxy object, which is injected into the Spring container. So we can inject it directly with @Autowired. You can also see that the above branch of code ends up in the following code:

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

Click through the targeter.target source code and you can see that what is actually being created is a proxy object, meaning that when the container starts, one is created for each @FeignClient. This concludes the core implementation of the Spring Cloud and Feign integration principles.

conclusion

This article mainly introduces the principle of Spring Cloud integrating Feign. From the above, you already know that Srpring creates a proxy object for our annotated @FeignClient interface, so with this proxy object we can do enhanced processing (e.g Do you know how this is done? If you are interested, you can look at the source code to find the answer (hint: the enhanced logic is in the InvocationHandler). Feign also collaborates with components like Ribbon and Hystrix. If you are interested, you can download the source code yourself.


Thanks for your thumb up, favorites and comments, and we’ll see you next week! This article continues to be updated, WeChat search “mghio” to pay attention to this “Java Porters & Lilong Learners”, together with awesome ~.