SpringCloud OpenFeign源码详细解析
简介
OpenFeign 是Spring Cloud中的一个组件,经常用来在项目中进行Http调用其他服务的接口,在于对服务端接口的绑定。
使用方式是比较简单的,让我们来探究一下使用原理吧!
阅读条件:
- 要有Spring源码的基础
- 使用过原生的Feign
- SpringBoot源码基础
源码解析入口:@EnableFeignClients
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
注解里面的参数配置,都可以在官网找到说明,这里就不介绍了。
点进去我们可以在注解上看到这样 一段代码:
@Import(FeignClientsRegistrar.class)
这个是往Spring容器里面注入一个类。
核心在于 FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar。
ImportBeanDefinitionRegistrar是Spring中的一个扩展点,实现该接口的方法,我们可以动态的往容器里面注入其他类的信息,然后走Spring的生命周期,创建出Bean。
要说明的一点实现了ImportBeanDefinitionRegistrar的类,该实现类的执行时机在于Spring对配置类的解析阶段执行的,熟悉Spring源码的同学都知道,Spring是先扫描合格的配置类,从配置类中获取要创建Bean的类信息,注册成BeanDefinition简称bd,最后在统一进行实例化。
Spring在解析配置类的时候,会判断该类是否实现了ImportBeanDefinitionRegistrar,是的话就创建对象,执行接口方法,这样也就会执行我们写的自定义逻辑,往容器里面注入bd。
我们直接找到 FeignClientsRegistrar 实现的注入bd方法:
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 对 @EnableFeignClients 解析
registerDefaultConfiguration(metadata, registry);
// 对 @FeignClient 解析
registerFeignClients(metadata, registry);
}
我们直接看对 @FeignClient 的解析。
1.@FeignClient
只看核心部分:
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 创建一个扫描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
// 扫描指定的包下面的 @FeignClient
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
// 核心方法:向容器中注入 FeignClientSpecification
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 核心方法:注册接口,注册到容器
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
/**
* 这个地方每一个 @FeignClient 都会向容器中注入一个 FeignClientSpecification 的用意是什么呢?
* 在 FeignAutoConfiguration 自动配置里面我们会进行分析,就是为了实现 容器配置隔离。
* 每一个 @FeignClient 如果指定了 configuration 属性的值,都会对应一个Spring容器,达到隔离的效果。
* 官网中为啥会说 如果@FeignClient中 指定了 configuration 对应的Class不需要加@Configuration注解呢?在下面我们会进行分析的
**/
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
// configuration 是 @FeignClient 中的 class<?>[] configuration() default {} 值;
// 向容器中注入 FeignClientSpecification
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
// 注入接口的实际bd类型是 FeignClientFactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
// AUTOWIRE_BY_TYPE,方法注入
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
// 构造了bd
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
上面就是扫描包,得到加了注解的类接口信息,然后根据每一个FeignClient,修改bd后向容器注入。
这一步比较重要的就是,统一修改了bd的类信息为FeignClientFactoryBean,并设置了各种参数。
2.FeignClientFactoryBean
重要的就是这个类的实例化,继承体系上我们可以看到FeignClientFactoryBean是一个FactoryBean,也就是说Spring在创建该类实例的时候,会去调FactoryBean的getObject()方法,下面只要重点分析getObject()方法即可。
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
// FeignContext 是从 FeignAutoConfiguration 注入进来的(重要)
FeignContext context = this.applicationContext.getBean(FeignContext.class);
// feign(context);原生api构建Feign Client
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
this.url += cleanPath();
// 服务名的调用方式,,负载均衡调用
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
// 指定了 url 方式
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
Targeter targeter = get(context, Targeter.class);
// 在往下面就是生成动态代理对象等
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
/**
*get(context, xxx.class) 是从容器中获取的对应的实现类,但并不是直接从当前容器中获取,
*前面说过 配置隔离,每一个@FeignClient 如果指定了 configuration 属性的值 都会生成
*一个Spring容器,会根据指定的名称,获取指定的Spring容器,然后从Spring容器里面获取这个xxx.class的实现
**/
protected Feign.Builder feign(FeignContext context) {
// get(context, FeignLoggerFactory.class); 是从Spring容器中获取的
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// 原生Feign的构建
Feign.Builder builder = get(context, Feign.Builder.class)
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
configureFeign(context, builder);
return builder;
}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
// 从容器中获取 Client.class,真正发起http请求的实现,这个地方可以进行负载均衡请求
// FeignRibbonClientAutoConfiguration: feign调用ribbon 负载均衡的自动配置
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
}
Client的实现:
FeignBlockingLoadBalancerClient
LoadBalancerFeignClient
3.FeignAutoConfiguration
public class FeignAutoConfiguration {
/**
* 这个地方注入进来了前面扫描到的每一个@FeignClient 修改bd过后的 FeignClientSpecification
**/
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
// 比较重要的一个类
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
}
FeignContext 继承 NamedContextFactory
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
public FeignContext() {
// 这个 FeignClientsConfiguration 是Spring默认为Feign提供的上下文信息
// 上下文信息里面有 feign.Decoder,feign.Encoder,feign.Contract 等等
// 我们没有指定的话,就会用这个类提供的
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
NamedContextFactory:命名空间类
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
private final String propertySourceName;
private final String propertyName;
// 拼接的名字,Spring上下文
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
// 拼接的名字,FeignClientSpecification
private Map<String, C> configurations = new ConcurrentHashMap<>();
// 父Spring上下文
private ApplicationContext parent;
// FeignClientsConfiguration
private Class<?> defaultConfigType;
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
String propertyName) {
this.defaultConfigType = defaultConfigType;
this.propertySourceName = propertySourceName;
}
// 上面的get(context, FeignLoggerFactory.class); 最终会掉这个方法先获取容器
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
// map 中不存在,就会重新创建
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
protected AnnotationConfigApplicationContext createContext(String name) {
// 手动创建 AnnotationConfigApplicationContext
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
// 注入configuration也就是@FeignClient指定的configuration属性的值
context.register(configuration);
}
}
// 默认的 FeignClientsConfiguration,也注入到容器
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
if (this.parent != null) {
// 设置父上下文,也就是当前项目启动的Spring容器
context.setParent(this.parent);
context.setClassLoader(this.parent.getClassLoader());
}
// 刷新容器
context.refresh();
return context;
}
}
createContext(String name)里解释了官网的这一段说明:
4. FeignRibbonClientAutoConfiguration
前面说了,真正发起 http 请求的类是 Client client = getOptional(context, Client.class);
public interface Client {
/**
* Executes a request against its {@link Request#url() url} and returns a response.
*/
Response execute(Request request, Options options) throws IOException;
}
我们来看一下FeignRibbonClientAutoConfiguration:
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
@Bean
@Primary
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory cachingLBClientFactory(
SpringClientFactory factory) {
return new CachingSpringLoadBalancerFactory(factory);
}
@Bean
@Primary
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
return new CachingSpringLoadBalancerFactory(factory, retryFactory);
}
}
导入了一个 DefaultFeignLoadBalancedConfiguration:
@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
/**
* 注入了一个具备负载均衡的Client:LoadBalancerFeignClient
**/
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
clientFactory);
}
}
FeignRibbonClientAutoConfiguration,FeignAutoConfiguration的加载时机:
基于SpringBoot的自动装配原理。
总结图
自此源码分析结束!