Feign series (05) Spring Cloud OpenFeign source parsing

Feign series (05) Spring Cloud OpenFeign source parsing

[TOC]

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

In the previous article we analyze the entire process Feign parameter parsing, Feign has native support Feign, JAX-RS 1/2 declarative specification, we focus on the integration of Spring Cloud is if OpenFeign, making support for Spring MVC?

1. Spring Cloud OpenFeign most simple to use

1.1 introduced maven

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

1.2 @EnableFeignClients scan package annotations

@SpringBootApplication
@EnableFeignClients  // 默认扫描 FeignApplication.class 包下 @FeignClient 注解
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: At this point, you can call the http like to use the same common interface

2. Feign whole assembly process analysis

FIG 1: Feign entire assembly process
sequenceDiagram participant FeignContext participant @EnableFeignClients participant FeignClientsRegistrar participant FeignClientFactoryBean participant DefaultTargeter participant Feign.Builder FeignContext ->> FeignContext: 1. FeignAutoConfiguration 自动注入 @EnableFeignClients ->> FeignClientsRegistrar: 2. import FeignClientsRegistrar ->> FeignClientFactoryBean: 3. 扫描 @FeignClient 注解 FeignClientFactoryBean ->> FeignClientFactoryBean: 4. getObject FeignContext ->> Feign.Builder: 5. 获取Feign.Builder:feign(context) FeignContext ->> DefaultTargeter: 6. 获取DefaultTargeter:get(context, Targeter.class) DefaultTargeter ->> Feign.Builder: 7. 创建代理对象:feign.target(target)

Summary: OpenFeign fitted with two inlets:

  1. @EnableAutoConfiguration automatic assembly (spring.factories)

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
    org.springframework.cloud.openfeign.FeignAutoConfiguration
    
    • FeignAutoConfiguration And automatic assembly FeignContext Targeter, and Client configuration.
      • FeignContext FeignClient the context of each assembly is, the default configuration is FeignClientsConfiguration
      • TargeterThere are two implementations: one DefaultTargeter, direct call Feign.Builder; the second is HystrixTargeter, call HystrixFeign.Builder, open fuse.
      • Client: Automatic assembly ApacheHttpClient, OkHttpClient, assembly conditions are not met, the default is Client.Default. But none of these Client load balancing.
    • FeignRibbonClientAutoConfiguration Load balancing, load balancing is achieved in this layer Client.
      • HttpClientFeignLoadBalancedConfiguration ApacheHttpClient load balancing
      • OkHttpFeignLoadBalancedConfiguration OkHttpClient load balancing
      • DefaultFeignLoadBalancedConfiguration Client.Default load balancing
  2. @EnableFeignClients automatic scanning

    @EnableFeignClients injection FeignClientsRegistrar, FeignClientsRegistrar enable automatic scanning, the packaging bag into the interface marked @FeignClient FeignClientFactoryBean objects, the proxy object interface finally generated through Feign.Builder. The default configuration Feign.Builder is FeignClientsConfiguration, it is automatically injected in the FeignAutoConfiguration.

Note: fusing and current limiting is FeignAutoConfiguration by injecting HystrixTargeter done, and load balancing is FeignRibbonClientAutoConfiguration injection.

3. Feign automatic assembly

3.1 FeignAutoConfiguration

3.1.1 FeignContext

// FeignAutoConfiguration 自动装配 FeignContext
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();

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

FeignContext Feign each client configuration context, will initialize a Feign components are initialized in a sub ApplicationContext to isolate different client Feign. In other words name, @FeignClient will initialize the name of a different son of the Spring container.

Note: Each client Feign addition to the default FeignClientsConfiguration, you can also customize the configuration class FeignClientSpecification, how these configurations are injected, will specify when @EnableFeignClients and @FeignClient source code analysis.

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

Summary: FeignClientsConfiguration Feign is the default configuration, you can modify the default configuration by @EnableFeignClients and @FeignClient. FeignClientsConfiguration main configuration is as follows:

@Configuration
public class FeignClientsConfiguration {
    @Bean
	@ConditionalOnMissingBean
	public Decoder feignDecoder() {
		return new OptionalDecoder(
				new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
	}
    // 适配 Spring MVC 注解
	@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: whether Fuse

// FeignAutoConfiguration 自动装配 Targeter
@Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
    @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 implemented in two: one DefaultTargeter, direct call Feign.Builder; the second is HystrixTargeter, call HystrixFeign.Builder, open fuse.

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

3.1.3 Client

// FeignClientsConfiguration 不实现负载均衡的 Client。OkHttpFeignConfiguration 类似
@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 can know, HttpClientFeign no 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 用于组装 FeignLoadBalancer
    @Bean
	@Primary
	@ConditionalOnMissingBean
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	public CachingSpringLoadBalancerFactory cachingLBClientFactory(
			SpringClientFactory factory) {
		return new CachingSpringLoadBalancerFactory(factory);
	}
}

Summary: FeignRibbonClientAutoConfiguration achieve load balancing. SpringClientFactory actually RibbonClientFactory, functionally equivalent to FeignContext, the basic components for assembling the Ribbon, SpringClientFactory this name is too misleading people.

Note that the import configuration based on three FeignRibbonClientAutoConfiguration, HttpClientFeignLoadBalancedConfiguration, OkHttpFeignLoadBalancedConfiguration, DefaultFeignLoadBalancedConfiguration.

@Configuration
class DefaultFeignLoadBalancedConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                              SpringClientFactory clientFactory) {
        // cachingFactory 用于组装 FeignLoadBalancer
        return new LoadBalancerFeignClient(new Client.Default(null, null), 
                   cachingFactory, clientFactory);
    }
}

4. source code analysis

@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    String[] basePackages() default {};			 // 包扫描路径
    Class<?>[] defaultConfiguration() default {};// 默认配置
}

Summary: From the properties substantially @EnableFeignClients can be inferred, all annotated classes @FeignClient under FeignClientsRegistrar scans basePackages package, with Feign.Builder generate dynamic agent is injected into the Bean Spring container. Is not it so? We look at.

4.2.1 FeignClientsRegistrar

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, 	
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        // 注册 @EnableFeignClients#defaultConfiguration 默认配置类
		registerDefaultConfiguration(metadata, registry);
        // 扫描所有的 @FeignClient 注解
		registerFeignClients(metadata, registry);
	}
}

(1) registerDefaultConfiguration

registerDefaultConfiguration final call registerClientConfiguration(registry, name,defaultAttrs.get("defaultConfiguration"))will default configuration @EnableFeignClients injected 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 when FeignAutoConfiguration automatic assembly FeignContext List<FeignClientSpecification> configurationsdo, is to register @EnableFeignClients and @FeignClient of configuration attributes to the Spring container.

(2) registerFeignClients

The label interface registerFeignClients @FeignClient assembled FeignClientFactoryBean poured into the container. FeignClientFactoryBean # getObject eventually calls feign.target generate the final proxy object.

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

    // 扫描条件: @FeignClient
    Set<String> basePackages;
    Map<String, Object> attrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName());
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
        FeignClient.class);
        scanner.addIncludeFilter(annotationTypeFilter);
        basePackages = getBasePackages(metadata);
    ...

    // 扫描 basePackage 下的 @FeignClient 注解
    for (String basePackage : basePackages) {
        Set<BeanDefinition> candidateComponents = scanner
            .findCandidateComponents(basePackage);
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                Map<String, Object> attributes = annotationMetadata
                    .getAnnotationAttributes(
                    FeignClient.class.getCanonicalName());
                String name = getClientName(attributes);
                // 注册 @FeignClient 的配置
                registerClientConfiguration(registry, name,
                                            attributes.get("configuration"));
                // 将该接口通过 FeignClientFactoryBean 注入到容器中
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

// 注册 FeignClientFactoryBean
private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    BeanDefinitionBuilder definition = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientFactoryBean.class);
    ...
}

Summary: At this point, we finally see Bean's registered, but have not seen feign.target generate dynamic proxy. We know FeignClientFactoryBean Spring's Bean factory class, you can get the real Bean by its getObject method. So be sure you can see the code in a similar feign.target getObject in.

4.2 FeignClientFactoryBean

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

<T> T getTarget() {
    // 1. FeignAutoConfiguration 自动装配 FeignContext
    FeignContext context = this.applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);
    
	// 2. url不存在,则一定是负载均衡
    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. 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) {
            client = ((LoadBalancerFeignClient) client).getDelegate();
        }
        builder.client(client);
    }
    // 4 FeignAutoConfiguration 自动装配 Targeter
    Targeter targeter = get(context, Targeter.class);
    // 调用 feign.target 生成动态代理
    return (T) targeter.target(this, builder, context,
                               new HardCodedTarget<>(this.type, this.name, url));
}

Summary: At this point, @ FeignClient tagging interface, and ultimately by targeter.target generate the final proxy object. There are two important objects and FeignClient Targeter in FeignClientFactoryBean, these two objects are automatically injected through FeignAutoConfiguration.

  1. FeignClient all @FeignClient of context, management of all configuration Feign.Builder. Depending @FeignClient (the same service) contextId distinction context, each environment is a child Spring container so until @FeignClient isolate different purposes. The default configuration is @FeignClient FeignClientsConfiguration, but can also be modified by the @FeignClient @EnableFeignClients properties and configuration.

    // NamedContextFactory#getContext 会根据 name 创建一个 ApplicationContext
    // FeignContext.getInstance(this.contextId, type),在本文中就是根据 contextId 区分
    protected AnnotationConfigApplicationContext getContext(String name) {
        if (!this.contexts.containsKey(name)) {
            synchronized (this.contexts) {
                if (!this.contexts.containsKey(name)) {
                    this.contexts.put(name, createContext(name));
                }
            }
        }
        return this.contexts.get(name);
    }
    
  2. Targeter can integrate Hystrix, current-limiting fuse achieve.


The intentions of recording a little bit every day. Perhaps the content is not important, but the habit is very important!

Guess you like

Origin www.cnblogs.com/binarylei/p/11594917.html