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 方法中注册的Configurationfor (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 beanscontext.setParent(this.parent);// jdk11 issue// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101context.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