FeIGN Series (05) Spring Cloud OpenFeign Source Code Analysis

Feign series (05) Spring Cloud OpenFeign source code analysis

[TOC]

Spring Cloud series Contents (https://www.cnblogs.com/binarylei/p/11563952.html#feign)

In the previous article, we analyzed the entire process of Feign parameter parsing, Feign native Already support Feign, JAX-RS 1/2 declarative specifications, this article focuses on how Spring Cloud integrates OpenFeign to support Spring MVC?

1. Spring Cloud OpenFeign is the simplest to use

1.1 Introducing maven

< br /> org.springframework.cloud
spring-cloud-starter-openfeign

1.2 @EnableFeignClients annotation scanning package

@SpringBootApplication
@EnableFeignClients // Scan the @FeignClient annotations under the FeignApplication.class package by default
public class FeignApplication {

public static void main(String[] args) {
SpringApplication.run(FeignApplication.class);
}
}

1.3 @ FeignClient configuration

@FeignClient(value = "echo-server",url = "http://127.0.0.1:10010")
public interface EchoService {

@GetMapping("/echo/{msg}")
String echo(@PathVariable String msg);
}

Summary: So far, it can be like Call http like a normal interface

2. Feign overall assembly process analysis

Figure 1: Feign overall assembly process

sequenceDiagram participant FeignContext participant @EnableFeignClients participant FeignClientsRegistrar participant FeignClientFactoryBean participant DefaultTargeter participant Feign.Builder FeignContext ->> FeignContext: 1. FeignAutoConfiguration automatically injects @EnableFeignClients ->> FeignClientsRegistrar: 2. import FeignClientsRegistrar ->> FeignClientFactoryBean: 3. Scan @FeignClient annotation FeignClientFactoryBean ->> FeignClientFactoryBean: 4. getObject FeignContext ->> Feign.Builder: 5. Get Feign.Builder: feign(context) FeignContext ->> DefaultTargeter: 6. Get DefaultTargeter: get(context , Targeter.class) DefaultTargeter ->> Feign.Builder: 7. Create proxy object: feign.target(target)

Summary: There are two entrances to the OpenFeign assembly: p>

  1. @EnableAutoConfiguration automatic assembly (spring.factories)

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,org .springframework.cloud.openfeign.FeignAu toConfiguration
    • FeignAutoConfiguration Automatically assemble FeignContext and Targeter, and Client configuration.
      • FeignContext is the assembly context of each FeignClient, the default configuration is FeignClientsConfiguration
      • Targeter There are two implementations: one is DefaultTargeter , Directly call Feign.Builder; The second is HystrixTargeter, call HystrixFeign.Builder to turn on the fuse.
      • Client: Automatically assemble ApacheHttpClient, OkHttpClient, the assembly conditions are not met, the default is Client.Default. However, none of these Clients achieve load balancing.
    • FeignRibbonClientAutoConfiguration To achieve load balancing, load balancing is implemented at the Client layer.
      • HttpClientFeignLoadBalancedConfiguration ApacheHttpClient implements load balancing
      • OkHttpFeignLoadBalancedConfiguration OkHttpClient implements load balancing
      • DefaultFeignLoadBalancedConfiguration Client.Default implements load balancing
  2. @EnableFeignClients automatic scanning

    @ EnableFeignClients injects FeignClientsRegistrar, FeignClientsRegistrar turns on automatic scanning, wraps the interface marked by @FeignClient under the package into FeignClientFactoryBean object, and finally generates the proxy object of this interface through Feign.Builder. The default configuration of Feign.Builder is FeignClientsConfiguration, which is automatically injected in FeignAutoConfiguration.

Note: Fuse and current limit are completed by FeignAutoConfiguration by injecting HystrixTargeter, while load balancing is injected by FeignRibbonClientAutoConfiguration.

3. Feign automatic assembly

3.1 FeignAutoConfiguration

3.1.1 FeignContext< /h4>

// FeignAutoConfiguration FeignContext
@Autowired(required = false)
private List configurations = new ArrayList<>();

@ Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}

FeignContext is the context environment configured by each Feign client. The components that initialize a Feign are initialized in a sub-ApplicationContext to isolate different Feign clients. In other words, @FeignClient with different names will initialize a child Spring container.

Note: In addition to the default FeignClientsConfiguration, each Feign client can also customize the configuration class FeignClientSpecification. How these configurations are injected will be in the @EnableFeignClients and @FeignClient source code analysis Specific instructions.

public class FeignContext extends NamedContextFactory {
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}

Summary: FeignClientsConfiguration is the default configuration of Feign, which can be modified through @EnableFeignClients and @FeignClient. The main configuration of FeignClientsConfiguration is as follows:

@Configuration
public class FeignClientsConfiguration {
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(
new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
// Adapt to Spring MVC annotations
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}

@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
}

3.1.2 Targeter: Fuse or not

// FeignAutoConfiguration Automatic assembly Targeter
@Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign" )
protected static class HystrixFeignTargeterConfigurati on {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}

@Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}

Summary: Targeter has two implementations: one is DefaultTargeter, which directly calls Feign. Builder; The second is HystrixTargeter, call HystrixFeign.Builder to turn on the fuse.

class DefaultTargeter implements Targeter {
@Override
public T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget target) {
return feign.target(target);
}
}

3.1.3 Client

// FeignClientsConfiguration does not implement load balancing clients. OkHttpFeignConfiguration is similar
@Configuration
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignConfiguration {
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(HttpClient httpClient) {
return new ApacheHttpClient(httpClient);
}
}

From the assembly conditions, we can know that HttpClientFeign does not implement load balancing.

3.2 FeignRibbonClientAutoConfiguration

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class )
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
// CachingSpringLoadBalancerFactory is used to assemble FeignLoadBalancer
@Bean
@Primary
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")< br /> public CachingSpringLoadBalancerFactory cachingLBClientFactory(
SpringClientFactory factory) {
return new CachingSpringLoadBalancerFactory(factory);
}
}

Summary: FeignRibbonClientAutoConfiguration realizes load balancing. SpringClientFactory is actually RibbonClientFactory. Its function is equivalent to FeignContext. It is used to assemble the basic components of Ribbon. The name SpringClientFactory is too misleading.

Note that three configuration classes are imported on FeignRibbonClientAutoConfiguration, HttpClientFeignLoadBalancedConfiguration, OkHttpFeignLoadBalancedConfiguration, DefaultFeignLoadBalancedConfiguration.

@Configuration
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
// cachingFactory is used to assemble FeignLoadBalancer
return new LoadBalancerFeignClient(new Client.Default(null, null),
cachingFactory, clientFactory);
}
}

< h2 id="section">4. Source code analysis

@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
String[] basePackages() default {}; // Package scanning path
Class[] defaultConfiguration() default {};// Default configuration
}

Summary: From the properties of @EnableFeignClients It can be roughly inferred that FeignClientsRegistrar will scan all @FeignClient annotated classes under the basePackages package, and use Feign.Builder to generate dynamic proxy beans and inject them into the Spring container. Is that right? Let's take a look.

4.2.1 FeignClientsRegistrar

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, 
@Override
public void registerBeanDefinitions(AnnotationMetadata metadataDefinition,
BeanRegistry registry) {
// Register @EnableFeignClients#defaultConfiguration default configuration class
registerDefaultConfiguration(metadata, registry);
// Scan all @FeignClient annotations
registerFeignClients(metadata, registry) ;
}
}

(1) registerDefaultConfiguration

registerDefaultConfiguration finally calls registerClientConfiguration(registry, name,defaultAttrs. get("defaultConfiguration")) Inject the default configuration of @EnableFeignClients into the container.

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder .addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition ());
}

Summary: Remember the List configurations when FeignAutoConfiguration automatically assembles FeignContext? The configuration properties of EnableFeignClients and @FeignClient are registered in the Spring container.

(2) registerFeignClients

registerFeignClients assembles the interface annotated by @FeignClient into FeignClientFactoryBean and injects it into the container. FeignClientFactoryBean#getObject will eventually call feign.target to generate the final proxy object.

public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);

// Scanning conditions: @FeignClient
Set basePackages;
Map attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
...

// Scan for @FeignClient annotations under basePackage
for (String basePackage: basePackages) {
Set candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent: candidateCo mponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Map attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
// Register @FeignClient configuration
registerClientConfiguration (registry, name,
attributes.get("configuration"));
// Inject this interface into the container through FeignClientFactoryBean
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}

// Register FeignClientFactoryBean
private void registerFeignC lient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean .class);
...
}

Summary: So far, we finally see the registration of Bean, but we haven’t seen feign yet. target generates a dynamic proxy. We know that FeignClientFactoryBean is Spring's Bean factory class, and the real Bean can be obtained through its getObject method. So you must see code similar to feign.target in getObject.

4.2 FeignClientFactoryBean

@Override
public Object getObject() throws Exception {
return getTarget();
}< br />
T getTarget() {
// 1. FeignAutoConfiguration automatically assembles FeignContext
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign .Builder builder = feign(context);

// 2. If url does not exist, it must be load balancing
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this .type, this.name, this.url));
}

// 3. The url exists without load balancing
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) {
client = ((LoadBalancerFeignClient) client).getDelegate();
}
builder.client( client);
}
// 4 FeignAutoConfiguration automatic assembly Targeter
Targeter targeter = get(context, Targeter.class);
// call feign.target to generate dynamic proxy
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}

< strong>Summary: So far, the interface marked by @FeignClient will finally generate the final proxy object through targeter.target. There are two important objects in FeignClientFactoryBean, FeignClient and Targeter, both of which are automatically injected through FeignAutoConfiguration.

  1. FeignClient is the context of all @FeignClient and manages all the configuration of Feign.Builder. Different context environments are distinguished according to the contextId of @FeignClient (same service), each environment is a child Spring container, so as to isolate different @FeignClient purposes. The default configuration of @FeignClient is FeignClientsConfiguration, but it can also be modified through the configuration attributes of @EnableFeignClients and @FeignClient.

    // NamedContextFactory#getContext will create an ApplicationContext based on name
    // FeignContext.getInstance(this.contextId, type), in this article, it is distinguished based on contextId
    protected AnnotationConfigApplicationContext getContext( String name) {
    if (!this.contexts.containsKey(name)) {
    synchronized (this.contexts) {
    if (!this.contexts.containsKey(name)) {< br /> this.contexts.put(name, createContext(name));
    }
    }
    }
    return this.contexts.get(name);
    }
  2. Targeter can integrate Hystrix to achieve fusing and current limiting.


Record a little bit of heart every day. Content may not be important, but habits are important!

sequenceDiagram participant FeignContext participant @EnableFeignClients participant FeignClientsRegistrar participant FeignClientFactoryBean participant DefaultTargeter participant Feign.Builder FeignContext ->> FeignContext: 1. FeignAutoConfiguration automatically injects @EnableFeignClients ->> FeignClientsRegistrar: 2. import FeignClientsRegistrar- >> FeignClientFactoryBean: 3. Scan @FeignClient annotation FeignClientFactoryBean ->> FeignClientFactoryBean: 4. getObject FeignContext ->> Feign.Builder: 5. Get Feign.Builder: feign(context) FeignContext ->> DefaultTargeter: 6. Get DefaultTargeter: get (context, Targeter.class) DefaultTargeter ->> Feign.Builder: 7. Create proxy object: feign.target(target)

Leave a Comment

Your email address will not be published.