Feign 系列(05)Spring Cloud OpenFeign 源碼解析
Spring Cloud 系列目錄(https://www.cnblogs.com/binarylei/p/11563952.html#feign)
在 上一篇 文章中我們分析 Feign 參數解析的整個流程,Feign 原生已經支持 Feign、JAX-RS 1/2 聲明式規范,本文着重關注 Spring Cloud 是如果整合 OpenFeign 的,使之支持 Spring MVC?
1. Spring Cloud OpenFeign 最簡使用
1.1 引入 maven
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1.2 @EnableFeignClients 注解掃描包
@SpringBootApplication
@EnableFeignClients // 默認掃描 FeignApplication.class 包下 @FeignClient 注解
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class);
}
}
1.3 @FeignClient 配置
@FeignClient(value = "echo-server",url = "http://127.0.0.1:10010")
public interface EchoService {
@GetMapping("/echo/{msg}")
String echo(@PathVariable String msg);
}
總結: 至此,可以像使用普通接口一樣調用 http 了
2. Feign 整體裝配流程分析
總結: OpenFeign 裝配有兩個入口:
-
@EnableAutoConfiguration 自動裝配(spring.factories)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\ org.springframework.cloud.openfeign.FeignAutoConfiguration
FeignAutoConfiguration
自動裝配 FeignContext 和 Targeter,以及 Client 配置。FeignContext
是每個 FeignClient 的裝配上下文,默認的配置是 FeignClientsConfigurationTargeter
有兩種實現:一是 DefaultTargeter,直接調用 Feign.Builder; 二是 HystrixTargeter,調用 HystrixFeign.Builder,開啟熔斷。Client
:自動裝配 ApacheHttpClient,OkHttpClient,裝配條件不滿足,默認是 Client.Default。但這些 Client 都沒有實現負載均衡。
FeignRibbonClientAutoConfiguration
實現負載均衡,負載均衡是在 Client 這一層實現的。HttpClientFeignLoadBalancedConfiguration
ApacheHttpClient 實現負載均衡OkHttpFeignLoadBalancedConfiguration
OkHttpClient實現負載均衡DefaultFeignLoadBalancedConfiguration
Client.Default實現負載均衡
-
@EnableFeignClients 自動掃描
@EnableFeignClients 注入 FeignClientsRegistrar,FeignClientsRegistrar 開啟自動掃描,將包下 @FeignClient 標注的接口包裝成 FeignClientFactoryBean 對象,最終通過 Feign.Builder 生成該接口的代理對象。而 Feign.Builder 的默認配置是 FeignClientsConfiguration,是在 FeignAutoConfiguration 自動注入的。
注意: 熔斷與限流是 FeignAutoConfiguration 通過注入 HystrixTargeter 完成的,而負載均衡是 FeignRibbonClientAutoConfiguration 注入的。
3. Feign 自動裝配
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 客戶端配置的上下文環境,會將初始化一個 Feign 的組件都在一個子 ApplicationContext 中初始化,從而隔離不同的 Feign 客戶端。換名話說,不同名稱的 @FeignClient 都會初始化一個子的 Spring 容器。
注意: 每個 Feign 客戶端除了默認 FeignClientsConfiguration,還可以自定義配置類 FeignClientSpecification,這些配置是如何注入的,會在 @EnableFeignClients 和 @FeignClient 源碼分析時具體說明。
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
總結: FeignClientsConfiguration 是 Feign 的默認配置,可以通過 @EnableFeignClients 和 @FeignClient 修改默認配置。FeignClientsConfiguration 主要配置如下:
@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:是否熔斷
// 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();
}
}
總結: Targeter 有兩種實現:一是 DefaultTargeter,直接調用 Feign.Builder; 二是 HystrixTargeter,調用 HystrixFeign.Builder,開啟熔斷。
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);
}
}
從裝配條件就可以知道,HttpClientFeign 沒有實現負載均衡。
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);
}
}
總結: FeignRibbonClientAutoConfiguration 實現了負載均衡。SpringClientFactory 實際是 RibbonClientFactory,功能等同於 FeignContext,用於裝配 Ribbon 的基本組件,SpringClientFactory 這個名稱太誤導人了。
注意在 FeignRibbonClientAutoConfiguration 之上 import 了三個配置類,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. 源碼分析
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
String[] basePackages() default {}; // 包掃描路徑
Class<?>[] defaultConfiguration() default {};// 默認配置
}
總結: 從 @EnableFeignClients 的屬性大致可以推斷出,FeignClientsRegistrar 會掃描 basePackages 包下的所有 @FeignClient 注解的類,用 Feign.Builder 生成動態代理的 Bean 注入到 Spring 容器中。是不是這樣呢?我們看一下。
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 最終調用 registerClientConfiguration(registry, name,defaultAttrs.get("defaultConfiguration"))
將 @EnableFeignClients 的默認配置注入到容器中。
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());
}
總結: 還記得 FeignAutoConfiguration 自動裝配 FeignContext 時的 List<FeignClientSpecification> configurations
嗎,就是將 @EnableFeignClients 和 @FeignClient 的 configuration 屬性注冊到 Spring 的容器中。
(2) registerFeignClients
registerFeignClients 將 @FeignClient 標注的接口裝配成 FeignClientFactoryBean 注入到容器中。FeignClientFactoryBean#getObject 最終會調用 feign.target 生成最終的代理對象。
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);
...
}
總結: 至此,我們總算看到 Bean 的注冊了,但還沒有看到 feign.target 生成動態代理。我們知道 FeignClientFactoryBean 是 Spring 的 Bean 工廠類,通過其 getObject 方法可以獲取真正的 Bean。所以在 getObject 中一定可以看到類似 feign.target 的代碼。
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));
}
總結: 至此,@FeignClient 標注的接口,最終通過 targeter.target 生成最終的代理對象。在 FeignClientFactoryBean 中有 2 個重要的對象 FeignClient 和 Targeter,這兩個對象都是通過 FeignAutoConfiguration 自動注入的。
-
FeignClient 是所有 @FeignClient 的上下文環境,管理了 Feign.Builder 的所有配置。根據 @FeignClient(同一個服務)的 contextId 區分不同的上下文環境,每個環境都是一個子 Spring 容器,從而直到隔離不同 @FeignClient 的目的。@FeignClient 的默認配置是 FeignClientsConfiguration,但同時也可以通過 @EnableFeignClients 和 @FeignClient 的 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 可以實現整合 Hystrix,實現熔斷與限流。
每天用心記錄一點點。內容也許不重要,但習慣很重要!