一、源码分析
1、 源码推演
我们在想为什么我们调用接口StockFeignClient就能调用对应的服务呢?
StockFeignClient接口代码如下:
@FeignClient(name = "msb-stock")//,configuration = StockFeignConfiguration.class) public interface StockFeignClient { /** * http://msb-stock/stock/reduce/{productId} * @param productId * @return */ @GetMapping("/stock/reduce/{productId}") String reduce(@PathVariable Integer productId); } 复制代码
StockFeignClient接⼝打个@FeignClient注解,它是怎么通过接⼝上的信息、找到接⼝的实现类的呢?我们看一下StockFeignClient发现⾥⾯就是⼀些SpringMVC相关的注解信息,⽐如接⼝类和⽅法上的@RequestMapping中、标注了具体访问时的路径以及请求⽅法(GET、PUT、POST、DELETE)是怎样的、@PathVariable标注了应该在请求路径上带上什么变量名、@RequestBody表示POST请求要带上的请求参数。
还有这个@FeignClient中name属性,这些信息一定是构建一个url, 好再@ReqeustMapping中我们知道我们的路径是/stock/reduce/{productId} 并且是一个get请求,
@GetMapping("/stock/reduce/{productId}") String reduce(@PathVariable Integer productId); 复制代码
那对应StockFeignClient上的注解@FeignClient注解,可以得到目标服务,也就是本次调用的服务msb-stock
@FeignClient(name = "msb-stock",configuration = StockFeignConfiguration.class) 复制代码
最终根据这些注解信息得到的请求URL信息为:/msb-stock//stock/reduce/12。
⽽⼜因为在SpringCloud体系内,发送⼀次请求都是通过HTTP协议来的,最终我们加上协议后,请求URL为: http://msb-stock//stock/reduce/45465。
并且我们是和nacos进行整合的,那么我们需要从nacos中获取所有服务对应的ip和port ,但是我们如果有多个实例那我们是不是需要利用负载均衡器来获取一个我们需要的服务,当然我们feign也整合了ribbon,所以我们底层可以使用ribbon进行负载均衡。
2、源码入口
源码分析的两种思路,一个是@EnableXXX 作为入口,另一个就是springboot的自动装配 xxxAutoConfiguration
进⼊到EnableFeignClients注解类中,会发现有个@Import注解,这个注解前面我们经常看到,这里导⼊了⼀个⽐较特别的类:FeignClietnsRegistrar,简单翻译下就是Feign的客户端注册器,注册器?这可能是吧@FeignClient注解标记的那些接口类,进行解析然后注入的
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({FeignClientsRegistrar.class}) public @interface EnableFeignClients { String[] value() default {}; String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; Class<?>[] defaultConfiguration() default {}; Class<?>[] clients() default {}; } 复制代码
3、扫描@FeignClient标注的类
因为他实现了ImportBeanDefinitionRegistrar 所以我们来看他的registerBeanDefinitions方法
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 解析默认的配置类EnableFeignClients registerDefaultConfiguration(metadata, registry); // 注册用@FeignClient标注的接口 registerFeignClients(metadata, registry); } 复制代码
3.1 整体思路分析
3.2 获取扫描器
registerFeignClients⽅法⼀进去我们可以看到getScanner⽅法、很明显它就是获取⼀个扫描器,在getScanner⽅法中,发现它new了⼀个
ClassPathScanningCandidateComponentProvider类型的对象,⾥⾯有个⽅法isCandidateComponent,来判断是否是我们需要的
protected ClassPathScanningCandidateComponentProvider getScanner() { return new ClassPathScanningCandidateComponentProvider(false, this.environment) { @Override protected boolean isCandidateComponent( AnnotatedBeanDefinition beanDefinition) { boolean isCandidate = false; // 确定基础类是否独立,即它是一个顶级类或嵌套类(静态内部类)可以独立于封闭类构造。 if (beanDefinition.getMetadata().isIndependent()) { // 判断是否是注解 if (!beanDefinition.getMetadata().isAnnotation()) { isCandidate = true; } } return isCandidate; } }; } 复制代码
3.3 获取扫描包
3.4 获取标注@FeignClient的接口并注入容器
获取标注@FeignClient的接口
这里的扫描我们能想起mybatis的扫描。
注入容器
这里设计的类是FeignClientFactoryBean 他是一个FactoryBean 我们获取对象是调用getObject
总结:
1.以启动类上的@EnableFeignClients为⼊⼝,扫描启动类所在包路径以及该包下所有⼦包中的所有的类
2.从扫描到的类中、筛选出所有打了@FeignClient注解的类
3.解析@FeignClient注解中的属性,创建⼀个BeanDefinition并设置各种属性值,再注⼊到Spring容器中