OpenFeign简介
OpenFeign 是一个声明式 RESTful 网络请求客户端。OpenFeign 会根据带有注解的函数信息构建出网络请求的模板,在发送网络请求之前,OpenFeign 会将函数的参数值设置到这些请求模板中。虽然 OpenFeign 只能支持基于文本的网络请求,但是它可以极大简化网络请求的实现,方便编程人员快速构建自己的网络请求应用。
核心组件与概念
在阅读源码时,可以沿着两条线路进行,一是被@FeignClient注解修饰的接口类如何创建,也就是其 Bean 实例是如何被创建的;二是调用这些接口类的网络请求相关函数时,OpenFeign 是如何发送网络请求的。而 OpenFeign 相关的类也可以以此来进行分类,一部分是用来初始化相应的 Bean 实例的,一部分是用来在调用方法时发送网络请求。
动态注册BeanDefinition
1. FeignClientsRegistrar
@EnableFeignClients
有三个作用,一是引入FeignClientsRegistrar;二是指定扫描FeignClient的包信息,就是指定FeignClient接口类所在的包名;三是指定FeignClient接口类的自定义配置类。@EnableFeignClients
注解的定义如下所示:
public @interface EnableFeignClients {
// 下面三个函数都是为了指定需要扫描的包
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
// 指定自定义feign client的自定义配置,可以配置 Decoder、Encoder和Contract等组件
// FeignClientsConfiguration 是默认的配置类
Class<?>[] defaultConfiguration() default {};
// 指定被@FeignClient修饰的类,如果不为空,那么路径自动检测机制会被关闭
Class<?>[] clients() default {};
}
// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerBeanDefinitions
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 从 EnableFeignClients 的属性值来构建 Feign 的自定义 Configuration 进行注册
registerDefaultConfiguration(metadata, registry);
// 扫描 package , 注册被 @FeignClient 修饰的接口类的Bean信息
registerFeignClients(metadata, registry);
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
// 使用 BeanDefinitionBuilder 来生成 BeanDefinition, 并注册到 registry 上
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
FeignClientSpecification 类实现了 NamedContextFactory.Specification 接口,它是 OpenFeign 组件实例化的重要一环,它持有自定义配置类提供的组件实例,供 OpenFeign 使用。SpringCloud 框架使用 NamedContextFactory 创建一系列的运行上下文,来让对应的 Specification 在这些上下文中创建实例对象。
// org.springframework.cloud.openfeign.FeignAutoConfiguration
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
// 创建 FeignContext 实例, 并将 FeignClientSpecification 注入
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
// org.springframework.cloud.openfeign.FeignContext#FeignContext
public FeignContext() {
// 将默认的 FeignClientsConfiguration 作为参数传递给构造函数
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
NamedContextFactory 是 FeignContext 的父类, 其 createContext 方法会创建具有名称为 Spring 的AnnotationConfigApplicationContext 实例作为当前上下文的子上下文。这些 AnnotationConfigApplicationContext 实例可以管理 OpenFeign 组件的不同实例。
NamedContextFactory 的实现代码如下:
// org.springframework.cloud.context.named.NamedContextFactory#createContext
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 获取该 name 所对应的 configuration ,如果有的话,就注册到子 context 中
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
context.register(configuration);
}
}
// 注册 default 的 Configuration, 也就是 FeignClientsRegistrar 类的 registerDefaultConfiguration 方法中注册的Configuration
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
// 注册 PropertyPlaceholderAutoConfiguration 和 FeignClientsConfiguration 配置类
context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
// 设置子 context 的 Environment 的 propertySource 属性源
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
// 所有 context 的 parent 都相同,这样的话,一些相同的Bean可以通过 parent context 来获取
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
2. 扫描类信息
FeignClientsRegistrar 做的第二件事情是扫描指定包下的类文件,注册 @FeignClient 注解修饰的接口类信息。
// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClients
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 自定义扫描类
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
// 获取 EnableFeignClients 配置信息
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
// 依照 Annotation 来进行 TypeFilter ,只会扫描出被 FeignClient 修饰的类
AnnotationTypeFilter annotationTypeFilter = n