spring

feign源码分析

2022-03-06  本文已影响0人  b6d5ffb96342

一、自动配置

要开始使用feign,需要在启动类上面增加@EnableFeignClients,该注解引入了FeignClientsRegistrar,来获取所有配置了@FeignClient的类,也就是所有的feign客户端。

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
        ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
    private ResourceLoader resourceLoader;
    private ClassLoader classLoader;
    private Environment environment;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        //如果指定了自己实现的feign配置
        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());
        //构建一个过滤器,过滤所有使用了@FeignClient的类
        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) {
            //获取所有扫描出的BeanDefinition,对每一个取出FeignClient的配置,注册FeignClientFactoryBean
            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);
                    //如果指定了自己实现的feign配置
                    registerClientConfiguration(registry, name,
                            attributes.get("configuration"));

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

上面是客户端被扫描并注册到容器的过程,细节见源码。下面我看一下feign是如何被引入和配置的。feign的配置类都在netflix-core包中

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix-core</artifactId>
</dependency> 

通过其中的spring.factory文件可以看到fein、ribbon、hystrix相关的配置文件。由自动配置源码可知,所有自动配置类会进行排序,ribbon的顺序在eureka之后,而feign没有指定和ribbon的顺序,因此feign在ribbon之后。我们先看一下ribbon的配置

public class RibbonAutoConfiguration{
    //由于我们没有使用原生ribbon的注解来设置ribbon客户端,这里应该是空的
    @Autowired(required = false)
    private List<RibbonClientSpecification> configurations = new ArrayList<>();
    //一个创建客户端的工厂类,具体意义不明
    @Bean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
    }
}

按照顺序之后回到feign的配置

public class FeignRibbonClientAutoConfiguration{
    //构建一个支持重试的ribbon客户端工厂,具体含义不明
    @Bean
    @Primary
    @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
    public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
        SpringClientFactory factory,
        LoadBalancedRetryPolicyFactory retryPolicyFactory,
        LoadBalancedBackOffPolicyFactory loadBalancedBackOffPolicyFactory,
        LoadBalancedRetryListenerFactory loadBalancedRetryListenerFactory) {
        return new CachingSpringLoadBalancerFactory(factory, retryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory);
    }
    
    //构建一个feign请求的默认配置,连接超时10s,读取超时6s
    @Bean
    @ConditionalOnMissingBean
    public Request.Options feignRequestOptions() {
        return LoadBalancerFeignClient.DEFAULT_OPTIONS;
    }
}

由FeignRibbonClientAutoConfiguration引入了HttpClientFeignLoadBalancedConfiguration,配置了httpclient相关。

@Configuration
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
class HttpClientFeignLoadBalancedConfiguration {
        //配置httpclient,有连接池配置
        @Bean
        @ConditionalOnProperty(value = "feign.compression.response.enabled", havingValue = "false", matchIfMissing = true)
        public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory, HttpClientConnectionManager httpClientConnectionManager,
                                              FeignHttpClientProperties httpClientProperties) {
            this.httpClient = createClient(httpClientFactory.createBuilder(), httpClientConnectionManager, httpClientProperties);
            return this.httpClient;
        }

        private CloseableHttpClient createClient(HttpClientBuilder builder, HttpClientConnectionManager httpClientConnectionManager,
                                                 FeignHttpClientProperties httpClientProperties) {
            RequestConfig defaultRequestConfig = RequestConfig.custom()
                    .setConnectTimeout(httpClientProperties.getConnectionTimeout())
                    .setRedirectsEnabled(httpClientProperties.isFollowRedirects())
                    .build();
            CloseableHttpClient httpClient = builder.setDefaultRequestConfig(defaultRequestConfig).
                    setConnectionManager(httpClientConnectionManager).build();
            return httpClient;
        }

        //构建支持负载均衡的feign客户端
        @Bean
        @ConditionalOnMissingBean(Client.class)
        public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
                                  SpringClientFactory clientFactory, HttpClient httpClient) {
            ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
            return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
        }
}

可以看到配置需要ApacheHttpClient类存在,因此必须引入

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

按照顺序,之后是FeignAutoConfiguration

@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
    //feign上下文,其中默认包含了feign的配置FeignClientsConfiguration
    @Bean
    public FeignContext feignContext() {
        FeignContext context = new FeignContext();
        context.setConfigurations(this.configurations);
        return context;
    }

    @Configuration
    @ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
    protected static class HystrixFeignTargeterConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public Targeter feignTargeter() {
            return new HystrixTargeter();
        }
    }
}

下面看一下hystrix的配置,它位于FeignClientsConfiguration中

@Configuration
public class FeignClientsConfiguration {
    @Configuration
    @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
    protected static class HystrixFeignConfiguration {
        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = false)
        public Feign.Builder feignHystrixBuilder() {
            return HystrixFeign.builder();
        }
    }
}

二、大致逻辑分析**

feign、ribbon、hystrix三者的关系大致如下

[图片上传失败...(image-924b74-1646649091496)]

ribbon作为底层的模块,我们首先分析ribbon

上一篇 下一篇

猜你喜欢

热点阅读