Feign source code in-depth analysis - core component: FeignClientFactoryBean

Posted by waltonia on Wed, 22 Dec 2021 01:14:11 +0100

"No step, no even a thousand miles."

As mentioned earlier, the @ EnableFeignClients annotation imports a component feignclientsregister into the container. This component implements the importbeandefinitionregister interface, and its registerBeanDefinitions() method will import some components into the spring container

registerFeignClients(metadata, registry);

This method will build a component scanner ClassPathScanningCandidateComponentProvider to scan the Feign client interface written by ourselves, that is, the interface with @ FeignClient annotation

After scanning, it will be put into a Set collection, then traverse the collection, and successively call the registerFeignClient() method to register it in the spring container

private void registerFeignClient(BeanDefinitionRegistry registry,
                                 AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    BeanDefinitionBuilder definition = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientFactoryBean.class);
    validate(attributes);
    definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes));
    String name = getName(attributes);
    definition.addPropertyValue("name", name);
    definition.addPropertyValue("type", className);
    definition.addPropertyValue("decode404", attributes.get("decode404"));
    definition.addPropertyValue("fallback", attributes.get("fallback"));
    definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

    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 });
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

As you can see, the method is not long

BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);

You can see a very special piece of code, FeignClientFactoryBean, which is a factory Bean. It is a special component in Spring. It is equivalent to registering all the scanned interfaces with @ FeignClient annotation written by ourselves as a factory Bean. You can directly call the getObject() method in subsequent use, When do you think this method will be called? Of course, it's time to DI (dependency injection) the Controller component!!! We'll verify this later

Then, the following series of addXXX() methods are a process of reading the attributes in the @ FeignClient annotation and putting them into the Bean definition information

For example, definition addPropertyValue(“name”, name); It is to put the service name into the bean definition information

Then getBeanDefinition() succeeded in getting an AbstractBeanDefinition. In fact, there is nothing to say. The spring basic operation includes the service name and other parameters we configured in the @ FeignClient annotation

Finally, it is encapsulated into a BeanDefinitionHolder and calls a spring tool class to register it in the container. BeanName is the fully qualified name of the class.

In the subsequent Controller, when Feign client needs to be used, you only need to declare an interface class, and then use @ Autowired annotation to inject the dynamic proxy into the Controller properties, which is called DI, dependency injection. Therefore, when instantiating the Controller component, you will call the getObject() method of FeignClientFactoryBean to obtain the proxy object

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

getTarget(), the logic to get the proxy object is in this method

/**
  * @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() {
    FeignContext context = applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);

    if (!StringUtils.hasText(this.url)) {
        String url;
        if (!this.name.startsWith("http")) {
            url = "http://" + this.name;
        }
        else {
            url = this.name;
        }
        url += cleanPath();
        return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
                                                                       this.name, 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 load 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 (T) targeter.target(this, builder, context, new HardCodedTarget<>(
        this.type, this.name, url));
}

@param the target type of the Feign client, the return value type is the feign client type, which is positive, otherwise it cannot be assigned to the Controller attribute

FeignContext context = applicationContext.getBean(FeignContext.class);

What does this line of code mean? I remember when I wrote ribbon earlier that a called service corresponds to a spring container, which contains its own components, ILoadBalancer, IRule, etc. the same is true here. Each FeignContext also represents an independent container of the called service, which contains its own components, such as encoder and decoder, Log component, annotation interpreter, etc

Then we call a feign() method to get a feign Builder

protected Feign.Builder feign(FeignContext context) {
    FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
    Logger logger = loggerFactory.create(this.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));
    // @formatter:on

    configureFeign(context, builder);

    return builder;
}

FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);

Here is to obtain its own FeignLoggerFactory component from the spring container corresponding to the called service

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

instance is the FeignLoggerFactory component in the spring container corresponding to the current service, and the getInstance() method is the component from the container, this Name is the service name, and type is FeignLoggerFactory Class, that is, the type of the component

public <T> T getInstance(String name, Class<T> type) {
    AnnotationConfigApplicationContext context = getContext(name);
    if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
                                                            type).length > 0) {
        return context.getBean(type);
    }
    return null;
}

Go on, logger logger = loggerfactory create(this.type);

Get a Logger component, which is used to record the logs generated during feign calls. Slf4jLogger is used by default

Feign.Builder builder = get(context, Feign.Builder.class)

Then get a feign. XML file from the spring container Builder component, which is a key line of code. This component must also be injected into the container through automatic configuration classes

At org springframework. cloud. A FeignClientsConfiguration is found under the openfeign package. The discovery means that a large number of components are configured, including feign Builder

@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
    return HystrixFeign.builder();
}
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
    return Feign.builder().retryer(retryer);
}

You will find that two builders have been found here, one is HystrixFeign, which is obviously used when integrating with Hystrix

The other is the default Builder. It just integrates a retrier, which is a retry mechanism. This is used by default,

Unless you have feign. In the configuration file hystrix. Enabled related configuration

.logger(logger)

.encoder(get(context, Encoder.class))

.decoder(get(context, Decoder.class))

.contract(get(context, Contract.class));

Then inject the logger, encoder, decoder and contract components into the feign In Builder

Of course, they are also obtained from the spring container corresponding to the called service

The default logger is Slf4jLogger

Spring encoder is used by default for encoder

By default, the coder uses the ResponseEntityDecoder

Spring mvccontract is used by default for contract

All of them are configured in the FeignClientsConfiguration configuration class. Previously, we told you about the default components in the preliminary chapter of feign. This time, we will take a look with the source code to verify our conclusions. Every article, word and conclusion I wrote in my blog come from the source code. See the source code! If you encounter problems at ordinary times, you should try your best to turn over the source code if you have the ability. Don't "face Baidu to become". It's not very good and you can't cultivate the ability

@Bean
@ConditionalOnMissingBean(FeignLoggerFactory.class)
public FeignLoggerFactory feignLoggerFactory() {
    return new DefaultFeignLoggerFactory(logger);
}
@Override
public Logger create(Class<?> type) {
    return this.logger != null ? this.logger : new Slf4jLogger(type);
}
@Bean
@ConditionalOnMissingBean
public Encoder feignEncoder() {
    return new SpringEncoder(this.messageConverters);
}
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
    return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
    return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}

ok, to sum up, the scanner scans the interfaces annotated with @ FeignClient, registers them as factory beans with l type FeignClientFactoryBean, and then calls the getObject() method when appropriate (when instantiating the Controller component, DI), This article mainly talks about how FeignClientFactoryBean, a component, obtains a FeignClient's dynamic agent. I'll control the length. It's not too long, otherwise it's easy to get tired of reading. The next article will continue to talk about the construction process of dynamic agent.

Finally, I would like to say a little more. In fact, writing this blog for source code analysis is very simple. It doesn't take much time to paste some things indiscriminately and write them according to my own ideas. However, when I finish writing, I can only understand it. Maybe the big man who has also studied the source code deeply can understand it. He is not friendly to most friends and can't get feedback

It's actually very difficult to write clearly and let most people understand. It's a test of some skills of bloggers

First of all, you must deeply understand the core source code. This is the most basic. It's not just looking at it, nor understanding parts, but the whole. You must have a certain grasp of the overall situation. Those who have written the source code analysis blog must have experience, which is more difficult than recording the source code video;

Secondly, it is to test the blogger's source code reading experience. We should learn to grasp the big and let go of the small. There are too many trivial things in the source code, everything is written, and there is no depth. We should filter it first, show the key things, and analyze the depth appropriately. If we can't analyze too many details, it is easy for readers to be confused and can't grasp the key points;

Finally, you have to understand the difficulties. It's one thing to understand yourself, and it's one thing to let others understand. I also modify and modify myself in writing. After writing, I look back several times for fear that I don't speak clearly. Moreover, the blogger is also an office worker. Please understand and forgive some defects. Of course, I will try my best to finish writing and review the manuscript repeatedly, and try to be easy to understand.

Topics: microservice