Feign如何对静态服务发起Http请求?

Feign单独使用时,可以对配置的静态服务发起Http调用;当Feign结合Ribbon时,对配置的静态服务可以根据负载均衡策略进行调用;再结合Eureka,可以配合注册中心以及负载均衡策略动态发起调用。本篇分析Feign对配置的静态服务是如何发起Http调用的

一、 Maven依赖

 <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Greenwich.SR3</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>

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

二 、自动配置类

Feign中的自动配置类是FeignAutoConfiguration,例举出其中重要的两个Bean

  • FeignContext
  • Targeter
    FeignContext内部还有内置配置类FeignClientsConfiguration,这是创建服务提供方IOC容器时会被解析的配置类,例举出几个重要的Bean
  • Decoder
  • Encoder
  • Feign.Builder
  • FeignLoggerFactory
  • Contract

三、 Bean释义

  • FeignContext

托管给容器的一个Bean,在以服务提供方为维度初始化子容器时会被使用到

@Bean
    public FeignContext feignContext() {
   
        FeignContext context = new FeignContext();
        context.setConfigurations(this.configurations);
        return context;
    }
  • Targeter

用以生成Fiegn代理类

@Bean
    @ConditionalOnMissingBean
    public Targeter feignTargeter() {
   
        return new HystrixTargeter();
    }

服务提供方维度的Bean

  • Decoder

编码器,用以对请求的参数做编码

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

解码器,用以对服务端的响应做解码

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

重试策略,默认不重试

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

用以封装FeignClient上所有配置信息

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

以类似于SpringMvc约定的参数格式来解析FeignClient接口的参数

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

关于Feign的使用在官网有介绍:需要建立合适的FeignClient,配置好静态服务的节点信息,最终使用注解开启Feign功能。然后便可以用方法调用的形式来代替传统编写Http请求的步骤,极大简化了开发过程。而关于整个Feign可以分为开启与使用两步,开启:对配置的服务提供方资源进行收集并创建代理类;使用:当调用方法是,交由实际创建的代理类来请求服务提供方的资源

四 、FeignClient类收集

Feign的开启需要使用注解@EnableFeignClients,这也是整个Feign的入口

  1. 在能被容器扫描的类上配置注解@EnableFeignClients后,启动服务
  2. Spring处理该类时,会从类上注解中收集导入的ImportBeanDefinitionRegistrar,随后调用它们的registerBeanDefinitions方法
  3. @EnableFeignClients上导入的类是FeignClientsRegistrar
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
   

日常使用中,一个FeignClient类对应于一个服务,类中不同的方法对应与服务中不同的接口。这样配置之后,对不同类不同方法的调用,可以达到对不同服务不同接口的发起Http调用。

  1. 在FeignClientsRegistrar的registerBeanDefinitions方法中,分别为注册默认配置类和注册FeignClient类
  2. 在registerFeignClients中注册FeignClient类
  3. 使用容器的Scanner,配合注解类型过滤器,从配置的或者默认的路径中,扫描出带有注解@FeignClient的类形成BeanDefinition
  4. 在registerFeignClient中尝试将这些BeanDefinition注册到容器中
  5. 在构造BeanDefinitionBuilder时,属性contextId实际是@FeignClient注解上配置的value或者name属性,用以区分服务提供方,BeanDefinitionBuilder持有的BeanDefinition的BeanClass已经被设置为FeignClientFactoryBean(这是一个FactoryBean,在Bean需要往容器托管时,其getObject方法会被调用),最后构造完成后注册到容器中。
@Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
   
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }

    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());
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
                FeignClient.class);
        final Class<?>[] clients = attrs == null ? null
                : (Class<?>[]) attrs.get("clients");
        if (clients == null || clients.length == 0) {
   
            scanner.addIncludeFilter(annotationTypeFilter);
            basePackages = getBasePackages(metadata);
        }
        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);
            for (BeanDefinition candidateComponent : candidateComponents) {
   
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
   
                    // 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());

                    String name = getClientName(attributes);
                    registerClientConfiguration(registry, name,
                            attributes.get("configuration"));

                    registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
    }

    private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map&
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值