OpenFeign是一个远程客户端请求代理,它的基本作用是让开发者能够以面向接口的方式来实现远程调用,从而屏蔽底层通信的复杂性,它的具体原理如下图所示。
在今天的内容中,我们需要详细分析OpenFeign它的工作原理及源码,我们继续回到这段代码。
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
IGoodsServiceFeignClient goodsServiceFeignClient;
@Autowired
IPromotionServiceFeignClient promotionServiceFeignClient;
@Autowired
IOrderServiceFeignClient orderServiceFeignClient;
/**
* 下单
*/
@GetMapping
public String order(){
String goodsInfo=goodsServiceFeignClient.getGoodsById();
String promotionInfo=promotionServiceFeignClient.getPromotionById();
String result=orderServiceFeignClient.createOrder(goodsInfo,promotionInfo);
return result;
}
}
从这段代码中,先引出对于OpenFeign功能实现的思考。
- 声明
@FeignClient
注解的接口,如何被解析和注入的? - 通过
@Autowired
依赖注入,到底是注入一个什么样的实例 - 基于FeignClient声明的接口被解析后,如何存储?
- 在发起方法调用时,整体的工作流程是什么样的?
- OpenFeign是如何集成Ribbon做负载均衡解析?
带着这些疑问,开始去逐项分析OpenFeign的核心源码
OpenFeign注解扫描与解析#
思考, 一个被声明了
@FeignClient
注解的接口,使用@Autowired
进行依赖注入,而最终这个接口能够正常被注入实例。
从这个结果来看,可以得到两个结论
- 被
@FeignClient
声明的接口,在Spring容器启动时,会被解析。 - 由于被Spring容器加载的是接口,而接口又没有实现类,因此Spring容器解析时,会生成一个动态代理类。
EnableFeignClient#
@FeignClient
注解是在什么时候被解析的呢?基于我们之前所有积累的知识,无非就以下这几种
- ImportSelector,批量导入bean
- ImportBeanDefinitionRegistrar,导入bean声明并进行注册
- BeanFactoryPostProcessor , 一个bean被装载的前后处理器
在这几个选项中,似乎ImportBeanDefinitionRegistrar
更合适,因为第一个是批量导入一个bean的string集合,不适合做动态Bean的声明。 而BeanFactoryPostProcessor
是一个Bean初始化之前和之后被调用的处理器。
而在我们的FeignClient声明中,并没有Spring相关的注解,所以自然也不会被Spring容器加载和触发。
那么
@FeignClient
是在哪里被声明扫描的呢?
在集成FeignClient时,我们在SpringBoot的main方法中,声明了一个注解@EnableFeignClients(basePackages = "com.gupaoedu.ms.api")
。这个注解需要填写一个指定的包名。
嗯,看到这里,基本上就能猜测出,这个注解必然和@FeignClient
注解的解析有莫大的关系。
下面这段代码是@EnableFeignClients
注解的声明,果然看到了一个很熟悉的面孔FeignClientsRegistrar
。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}
FeignClientsRegistrar#
FeignClientRegistrar,主要功能就是针对声明@FeignClient
注解的接口进行扫描和注入到IOC容器。
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
}
果然,这个类实现了ImportBeanDefinitionRegistrar
接口
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
this.registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
这个接口有两个重载的方法,用来实现Bean的声明和注册。
简单给大家演示一下ImportBeanDefinitionRegistrar的作用。
在
gpmall-portal
这个项目的com.gupaoedu
目录下,分别创建
- HelloService.java
- GpImportBeanDefinitionRegistrar.java
- EnableGpRegistrar.java
- TestMain
- 定义一个需要被装载到IOC容器中的类HelloService
public class HelloService {
}
- 定义一个Registrar的实现,定义一个bean,装载到IOC容器
public class GpImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition=new GenericBeanDefinition();
beanDefinition.setBeanClassName(HelloService.class.getName());
registry.registerBeanDefinition("helloService",beanDefinition);
}
}
- 定义一个注解类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {
}
- 写一个测试类
@Configuration
@EnableGpRegistrar
public class TestMain {
public static void main(String[] args) {
ApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestMain.class);
System.out.println(applicationContext.getBean(HelloService.class));
}
}
- 通过结果演示可以发现,
HelloService
这个bean 已经装载到了IOC容器。
这就是动态装载的功能实现,它相比于@Configuration配置注入,会多了很多的灵活性。 ok,再回到FeignClient的解析中来。
FeignClientsRegistrar.registerBeanDefinitions#
registerDefaultConfiguration
方法内部从SpringBoot
启动类上检查是否有@EnableFeignClients
, 有该注解的话, 则完成Feign
框架相关的一些配置内容注册。registerFeignClients
方法内部从classpath
中, 扫描获得@FeignClient
修饰的类, 将类的内容解析为BeanDefinition
, 最终通过调用 Spring 框架中的BeanDefinitionReaderUtils.resgisterBeanDefinition
将解析处理过的FeignClient BeanDeifinition
添加到spring
容器中
//BeanDefinitionReaderUtils.resgisterBeanDefinition
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//注册@EnableFeignClients中定义defaultConfiguration属性下的类,包装成FeignClientSpecification,注册到Spring容器。
//在@FeignClient中有一个属性:configuration,这个属性是表示各个FeignClient自定义的配置类,后面也会通过调用registerClientConfiguration方法来注册成FeignClientSpecification到容器。
//所以,这里可以完全理解在@EnableFeignClients中配置的是做为兜底的配置,在各个@FeignClient配置的就是自定义的情况。
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
这里面需要重点分析的就是
registerFeignClients
方法,这个方法主要是扫描类路径下所有的@FeignClient
注解,然后进行动态Bean的注入。它最终会调用registerFeignClient
方法。
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerFeignClient(registry, annotationMetadata, attr