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
Summary: OpenFeign fitted with two inlets:
@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 FeignClientsConfigurationTargeter
There 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 balancingOkHttpFeignLoadBalancedConfiguration
OkHttpClient load balancingDefaultFeignLoadBalancedConfiguration
Client.Default load balancing
@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> configurations
do, 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.
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); }
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!