spring-cloud-starter-openfeign 源码详细讲解

1.测试环境搭建:

1.1 架构图:

 

 

 

 

  product服务提供一个接口:

 

 

 order服务通过feign的方式来调用product的接口:

 

 

 order服务需要引入依赖:

       <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

order服务对外的controller :

 

 

order服务主启动类:

 2. 源码分析

          2.1: spring 是如何找到@FeignClient标注的接口?
             我们在order服务启动类中加了一个注解:@EnableFeignClients,点击该注解进入看看

    

 

 

   由图可知,该注解向容器导入了FeignClientsRegistrar.class类,然后我们跟踪进入该类:

 

 

 该类实现了ImportBeanDefinitionRegistrar接口,该接口有个向spring容器注册的组件的方法:

 debug调试:发现metadata就是主启动类的信息

 先跟踪第一个方法:registerDefaultConfiguration(metadata, registry);

 

 

 上图:通过主启动类获取@EnableFeignClients注解的defaultConfiguration属性,同时创建了一个名叫name的变量,值为:default.+主启动类的全限定类名

 继续跟进去:

 

 上图:向sprin容器中注册了一个FeignClientSpecification类,beanName为:default.com.yang.xiao.hui.order.OrderApplication.FeignClientSpecification

 

 @EnableFeignClients注解的defaultConfiguration属性我并没有配置信息,所以FeignClientSpecification的属性值是一个空的数组

第一个方法分析完后,下面分析第二个:

 

 

 

public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider scanner = getScanner(); //创建一个扫描器
        scanner.setResourceLoader(this.resourceLoader);

        Set<String> basePackages;

        Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());  //获取EnableFeignClients注解的元素据,因为该注解可以包含你要扫描的路径
                
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class); //注解过滤器,代表要过滤含有FeignClient.class的类
               final Class<?>[] clients = attrs == null ? null
                : (Class<?>[]) attrs.get("clients");
        if (clients == null || clients.length == 0) { //由于我没有配置对应的属性,所以会走这个分支
            scanner.addIncludeFilter(annotationTypeFilter); //扫描器将注解过滤器新增进来了,这个是重点了
            basePackages = getBasePackages(metadata); //EnableFeignClients注解的value,basePackages,basePackageClasses属性有值,就扫描这些配置的包名,如果没设置就扫描主启动类的包名,此处我没有配置,所以这里得到的是主启动类的报名
        }
        else {
            final Set<String> clientClasses = new HashSet<>();
            basePackages = new HashSet<>();
            for (Class<?> clazz : clients) {
                basePackages.add(ClassUtils.getPackageName(clazz));
                clientClasses.add(clazz.getCanonicalName());
            }
            AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
                @Override
                protected boolean match(ClassMetadata metadata) {
                    String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                    return clientClasses.contains(cleaned);
                }
            };
            scanner.addIncludeFilter(
                    new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
        }

//遍历所有要扫描的包名
for (String basePackage : basePackages) { Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); //通过扫描器,读取包名下所有的类,然后看看哪些是有FeignClient.class注解的,将这些类转成beanDefinition对象 for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { //这些BeanDefinition 都含有FeignClient.class注解 // verify annotated class is an interface AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes( FeignClient.class.getCanonicalName()); //获取FeignClient.class注解元信息 String name = getClientName(attributes);//获取FeignClient.class的name值,我这里配置的是@FeignClient(name ="product" ),所以name就是product registerClientConfiguration(registry, name, attributes.get("configuration"));//这个跟之前分析的第一个方法逻辑是一样的:向容器注入了一个name=product.feignClientSpecification 类型feignClientSpecification的bean registerFeignClient(registry, annotationMetadata, attributes);//注册所有的带有@FeignClient(name ="product" )注解的bean } } } }

 上面代码总结:主要做了:

              1.创建了一个扫描器ClassPathScanningCandidateComponentProvider scanner,并将AnnotationTypeFilter过滤器传入扫描器中(scanner.addIncludeFilter(annotationTypeFilter)),用于过滤扫描到的带有@FeignClient注解的类

              2.获取要扫描的包路径:basePackages = getBasePackages(metadata);

 

 

      
             3.遍历包名获取候选的带有FeignClient注解的类的信息 Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);

 

 

 因为扫描器之前加了一个注解过滤器,这里isCandidateComponent(metadataReader)的逻辑是使用注解过滤器来过滤出带有FeignClien注解的类

 

 

 

 4.遍历扫描到的BeanDefinition,向ioc容器中注册对应的bean,这里每个带有@FeignClient的类都会注册2个bean

  registerClientConfiguration(registry, name,attributes.get("configuration")); 向容器中注册了name=product.feignClientSpecification 类型feignClientSpecification的bean

重点是: registerFeignClient(registry, annotationMetadata, attributes);方法

 

 

 

 

 所以,该方法向spring容器注入的bean,名字为:com.yang.xiao.hui.order.controller.ProductService,类型为FeignClientFactoryBean,而FactoryBean都会提供一个getObject方法获取对应的实例

 spring如何获取带有@FeignClient注解标准的接口,总结如下图:

 

 3.springboot自动装配机制:springboot启动时会加载类路径下/META-INF/spring.factories中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类:

 

 

 

 

 因此有四个相关的配置类会被加载,我们主要关注2个FeignRibbonClientAutoConfiguration 和FeignAutoConfiguration

 

 

 //再看看DefaultFeignLoadBalancedConfiguration这个类:

 

 之后看看这个bean: FeignAutoConfiguration

 

 总结:自动装配机制注入的bean:

 

 

 4.获取第三方服务实例

                           4.1由前面分析,每个带有@FeignClient注解的接口都会被注入到spring容器,名字为接口的权限定类名,类型为FeignClientFactoryBean

                   

 

         
                  4.2我们debug调试,从容器中根据beanName获取对应的实例看看,我这里的beanName=com.yang.xiao.hui.order.controller.ProductService,order服务的controller方法先修改下:

      

 

                  跟进该方法:

                

 

              

 

           

 

          对于factoryBean,如果beanName前面带上“&” 符号,获取的bean就是原始的factoryBean,如果没带该符号,获取的是factoryBean.getObject()方法返回的bean,我们这次调试是没带上该符合的

       

 

 

       

 

 

      

 

 

       至此,我们可以知道,所有带有@FeignClient注解的接口获取实例,最终都是调用FeignClientFactoryBean的getObject方法,该方法才是我们的重点

         4.3 FeignClientFactoryBean的getObject()方法的讲解:

    

 

 

   

 

 分析: Feign.Builder builder = feign(context);

 

 

 //上面很多bean都是通过get(Context,xxx.class)方法获取,而context就是FeignContext,在springBoot自动装配时注入了spring容器,那么我们跟踪第一个方法看看:

FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);

 


 

 

 这里有2个疑问:这个容器是怎么创建的,为何能通过该容器获取对应的bean

 

 由于上面的获取子容器的方法是在FeignContext中的,所以我们要来分析下FeignContext的创建过程:

 

 FeignContext创建时调用了父类的有参构造

 

 

此时们再回到,通过服务名称获取子容器的方法,获取不到就创建一个子容器:

 

 由图可知,子容器注入了一个FeignClientsConfiguration配置类,该类是由FeignContext初始化时,调用父类有参构造器传入的,所以分析下该配置类:

@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {

    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    @Autowired(required = false)
    private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();

    @Autowired(required = false)
    private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();

    @Autowired(required = false)
    private Logger logger;

    @Autowired(required = false)
    private SpringDataWebProperties springDataWebProperties;

    @Bean
    @ConditionalOnMissingBean
    public Decoder feignDecoder() {
        return new OptionalDecoder(
                new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
    public Encoder feignEncoder() {
        return new SpringEncoder(this.messageConverters);
    }

    @Bean
    @ConditionalOnClass(name = "org.springframework.data.domain.Pageable")
    @ConditionalOnMissingBean
    public Encoder feignEncoderPageable() {
        PageableSpringEncoder encoder = new PageableSpringEncoder(
                new SpringEncoder(this.messageConverters));
        if (springDataWebProperties != null) {
            encoder.setPageParameter(
                    springDataWebProperties.getPageable().getPageParameter());
            encoder.setSizeParameter(
                    springDataWebProperties.getPageable().getSizeParameter());
            encoder.setSortParameter(
                    springDataWebProperties.getSort().getSortParameter());
        }
        return encoder;
    }

    @Bean
    @ConditionalOnMissingBean
    public Contract feignContract(ConversionService feignConversionService) {
        return new SpringMvcContract(this.parameterProcessors, feignConversionService);
    }

    @Bean
    public FormattingConversionService feignConversionService() {
        FormattingConversionService conversionService = new DefaultFormattingConversionService();
        for (FeignFormatterRegistrar feignFormatterRegistrar : this.feignFormatterRegistrars) {
            feignFormatterRegistrar.registerFormatters(conversionService);
        }
        return conversionService;
    }

    @Bean
    @ConditionalOnMissingBean
    public Retryer feignRetryer() {
        return Retryer.NEVER_RETRY;
    }

    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public Feign.Builder feignBuilder(Retryer retryer) {
        return Feign.builder().retryer(retryer);
    }

    @Bean
    @ConditionalOnMissingBean(FeignLoggerFactory.class)
    public FeignLoggerFactory feignLoggerFactory() {
        return new DefaultFeignLoggerFactory(this.logger);
    }

    @Bean
    @ConditionalOnClass(name = "org.springframework.data.domain.Page")
    public Module pageJacksonModule() {
        return new PageJacksonModule();
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {

        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled")  //这里可以发现,如果要想让hystrix生效,需要配置一下属性feign.hystrix.enabled
        public Feign.Builder feignHystrixBuilder() {
            return HystrixFeign.builder();
        }

    }

}

通过该配置类,可以知道,子容器中注入了Decoder,Encoder,Contract,FormattingConversionService,Retryer,FeignLoggerFactory,Module

此时我们再看看父容器和子容器注入的主要类:

 

 有了上面的分析,我们回到之前的方法继续分析:

 

 

我们看到该方法:configureUsingProperties(properties.getConfig().get(this.contextId),builder);,通过一个ContextId也就是服务名,我这里是product,获取该服务的专有配置,说明每一个服务都可以拥有自己的特殊配置;
看看这个FeignClientProperties类:

 

 

 

 

我们再次回到FactoryBean的getObject()方法进行分析:

 

 

 

上图,我们看到从容器中获取了一个client和targeter,这2个bean是在父容器中获取的,前面有分析过了:

 

 继续跟进:

 

 

 

 可见是调用了ReflectiveFeign的newInstance(target);方法

 

 

 至此,我们可以发现,最终是调用了JDK动态代理机制来生成代理类,下面再来分析上面2个方法:

Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);和InvocationHandler handler = factory.create(target, methodToHandler);
先看第一个方法:targetToHandlersByName.apply(target)

 


 

 

 我这里只有一个方法:

 

 所以跟进去:

 

 可见,校验是对RequestMaping注解的校验

 

 

我们分析对方法上的处理:

 

 之后我们再分析InvocationHandler handler = factory.create(target, methodToHandler);方法:

 

 

 

 

 对上述所有分析做个总结:

 

 5.我们是如何通过feign调用到第三方服务的,在这里就是我们是如何通过order服务调用到product服务:

                5.1:调用任何服务都要知道ip和端口,因此,如何获取ip和端口,如果服务提供者有多个,如何进行负载均衡,构建测试代码如下,在order服务下:

 

 

 

 

 

 

 

 

 

 

 

 我们看看server是如何获取到的,这里用到ribbon进行负载均衡,点击command.submit

 

 

 

 

 

 上图是ribbon获取负载均衡获取单个服务实例的过程,可以参考我之前的博客,有详细源码讲解:https://www.cnblogs.com/yangxiaohui227/p/12614343.html

 

 继续跟踪,到达了:

 

 

 

 最终是使用HttpURLConnection发起请求:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

               

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

 














posted @ 2020-05-26 14:19  yangxiaohui227  阅读(1195)  评论(0编辑  收藏  举报