Feign初始化解析

0 依赖引入

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                Greenwich.SR2
                pom
                import
            
        
    
    
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        

首先来分析一下pom中如何引入feign的依赖包。如上所示,我这边使用的是spring-cloud的Greenwich.SR2版本,通过spring-cloud-starter-openfeign引入feign,后续分析也基于该版本进行。

那么有必要来看一下spring-cloud-starter-openfeign.pom的定义


    4.0.0
    
        org.springframework.cloud
        spring-cloud-openfeign
        2.1.2.RELEASE
        ..
    
    spring-cloud-starter-openfeign
    Spring Cloud Starter OpenFeign
    Spring Cloud Starter OpenFeign
    https://projects.spring.io/spring-cloud
    
        Pivotal Software, Inc.
        https://www.spring.io
    
    
        ${basedir}/../..
    
    
        
            org.springframework.cloud
            spring-cloud-starter
        
        
            org.springframework.cloud
            spring-cloud-openfeign-core
        
        
            org.springframework
            spring-web
        
        
            org.springframework.cloud
            spring-cloud-commons
        
        
            io.github.openfeign
            feign-core
        
        
            io.github.openfeign
            feign-slf4j
        
        
            io.github.openfeign
            feign-hystrix
        
        
            io.github.openfeign
            feign-java8
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-ribbon
            true
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-archaius
            true
        
    

根据springboot-starter-*的加载原理(可以参考我之前的文章《Springboot-starter-xxx原理解析》)我们知道springboot加载的是引入包下META-INF/spring.factories文件,我们这里对应的就是spring-cloud-openfeign-core包,来看看其spring.factories的内容。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration

这里的FeignAcceptGzipEncodingAutoConfigurationFeignContentGzipEncodingAutoConfiguration用于请求和响应的压缩暂时不做说明,我们来看看FeignRibbonClientAutoConfigurationFeignAutoConfiguration都做了些什么。

1.1 FeignRibbonClientAutoConfiguration
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
        OkHttpFeignLoadBalancedConfiguration.class,
        DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {

    @Bean
    @Primary
    @ConditionalOnMissingBean
    @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    public CachingSpringLoadBalancerFactory cachingLBClientFactory(
            SpringClientFactory factory) {
        return new CachingSpringLoadBalancerFactory(factory);
    }

    @Bean
    @Primary
    @ConditionalOnMissingBean
    @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
    public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
            SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
        return new CachingSpringLoadBalancerFactory(factory, retryFactory);
    }

    @Bean
    @ConditionalOnMissingBean
    public Request.Options feignRequestOptions() {
        return LoadBalancerFeignClient.DEFAULT_OPTIONS;
    }

}

FeignHttpClientProperties定义了http请求的默认参数,包括最大连接数、连接超时时间等。

通过@AutoConfigureBefore注解我们可以看到该类的初始化要求是在FeignAutoConfiguration之前的。

通过@Import注解引入了HttpClientFeignLoadBalancedConfigurationOkHttpFeignLoadBalancedConfigurationDefaultFeignLoadBalancedConfiguration几个配置类,但前两个类是包含条件注解@ConditionalOnClass的,对应条件分别是ApacheHttpClient.classOkHttpClient.class,也就是当选择http实现为ApacheHttpClient和OkHttpClient时才会触发初始化,当我们选择默认的http实现时,仅会触发DefaultFeignLoadBalancedConfiguration的初始化,那么来看一下该类:

@Configuration
class DefaultFeignLoadBalancedConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
            SpringClientFactory clientFactory) {
        return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
                clientFactory);
    }

}

这里实例化了feignClient为一个LoadBalancerFeignClient类型的对象。

最后看一下cachingLBClientFactoryretryabeCachingLBClientFactory的初始化,根据@ConditionalOnMissingClass@ConditionalOnClass值均为org.springframework.retry.support.RetryTemplate可以看出这两个类是互斥的,根据RetryTemplate类的存在与否仅会有一个bean被实例化。RetryTemplate是spring5+版本增加的功能,需要另外引入spring-retry包。

1.2 FeignAutoConfiguration
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
        FeignHttpClientProperties.class })
public class FeignAutoConfiguration {

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

    @Bean
    public HasFeatures feignFeature() {
        return HasFeatures.namedFeature("Feign", Feign.class);
    }

    @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();
        }

    }

    @Configuration
    @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
    protected static class DefaultFeignTargeterConfiguration {

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

    }

    // the following configuration is for alternate feign clients if
    // ribbon is not on the class path.
    // see corresponding configurations in FeignRibbonClientAutoConfiguration
    // for load balanced ribbon clients.
    @Configuration
    @ConditionalOnClass(ApacheHttpClient.class)
    @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
    @ConditionalOnMissingBean(CloseableHttpClient.class)
    @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
    protected static class HttpClientFeignConfiguration {

        private final Timer connectionManagerTimer = new Timer(
                "FeignApacheHttpClientConfiguration.connectionManagerTimer", true);

        @Autowired(required = false)
        private RegistryBuilder registryBuilder;

        private CloseableHttpClient httpClient;

        @Bean
        @ConditionalOnMissingBean(HttpClientConnectionManager.class)
        public HttpClientConnectionManager connectionManager(
                ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
                FeignHttpClientProperties httpClientProperties) {
            final HttpClientConnectionManager connectionManager = connectionManagerFactory
                    .newConnectionManager(httpClientProperties.isDisableSslValidation(),
                            httpClientProperties.getMaxConnections(),
                            httpClientProperties.getMaxConnectionsPerRoute(),
                            httpClientProperties.getTimeToLive(),
                            httpClientProperties.getTimeToLiveUnit(),
                            this.registryBuilder);
            this.connectionManagerTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    connectionManager.closeExpiredConnections();
                }
            }, 30000, httpClientProperties.getConnectionTimerRepeat());
            return connectionManager;
        }

        @Bean
        public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
                HttpClientConnectionManager httpClientConnectionManager,
                FeignHttpClientProperties httpClientProperties) {
            RequestConfig defaultRequestConfig = RequestConfig.custom()
                    .setConnectTimeout(httpClientProperties.getConnectionTimeout())
                    .setRedirectsEnabled(httpClientProperties.isFollowRedirects())
                    .build();
            this.httpClient = httpClientFactory.createBuilder()
                    .setConnectionManager(httpClientConnectionManager)
                    .setDefaultRequestConfig(defaultRequestConfig).build();
            return this.httpClient;
        }

        @Bean
        @ConditionalOnMissingBean(Client.class)
        public Client feignClient(HttpClient httpClient) {
            return new ApacheHttpClient(httpClient);
        }

        @PreDestroy
        public void destroy() throws Exception {
            this.connectionManagerTimer.cancel();
            if (this.httpClient != null) {
                this.httpClient.close();
            }
        }

    }

    @Configuration
    @ConditionalOnClass(OkHttpClient.class)
    @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
    @ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
    @ConditionalOnProperty("feign.okhttp.enabled")
    protected static class OkHttpFeignConfiguration {

        private okhttp3.OkHttpClient okHttpClient;

        @Bean
        @ConditionalOnMissingBean(ConnectionPool.class)
        public ConnectionPool httpClientConnectionPool(
                FeignHttpClientProperties httpClientProperties,
                OkHttpClientConnectionPoolFactory connectionPoolFactory) {
            Integer maxTotalConnections = httpClientProperties.getMaxConnections();
            Long timeToLive = httpClientProperties.getTimeToLive();
            TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
            return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
        }

        @Bean
        public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
                ConnectionPool connectionPool,
                FeignHttpClientProperties httpClientProperties) {
            Boolean followRedirects = httpClientProperties.isFollowRedirects();
            Integer connectTimeout = httpClientProperties.getConnectionTimeout();
            Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
            this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation)
                    .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                    .followRedirects(followRedirects).connectionPool(connectionPool)
                    .build();
            return this.okHttpClient;
        }

        @PreDestroy
        public void destroy() {
            if (this.okHttpClient != null) {
                this.okHttpClient.dispatcher().executorService().shutdown();
                this.okHttpClient.connectionPool().evictAll();
            }
        }

        @Bean
        @ConditionalOnMissingBean(Client.class)
        public Client feignClient(okhttp3.OkHttpClient client) {
            return new OkHttpClient(client);
        }

    }

}

接着来看FeignAutoConfiguration类,这里牵涉到4个内部类的初始化,其中HttpClientFeignConfigurationOkHttpFeignConfiguration对应需要ApacheHttpClient.classOkHttpClient.class类的存在,因此在默认情况下并不会执行初始化,暂不做分析。接着看一下HystrixFeignTargeterConfigurationDefaultFeignTargeterConfiguration类,同样是根据feign.hystrix.HystrixFeign类存在与否,二选一进行初始化。HystrixFeign这个类在jar包feign.hystrix下,而spring-cloud-starter-openfeign.pom中正好有引入该包,那么这里很自然的就会选择初始化HystrixFeignTargeterConfiguration类,同时初始化feignTargeter为一个HystrixTargeter类型的对象。

2 FeignClientsConfiguration

除了上述加载的类以外,spring-cloud-openfeign-core包下还有一个类也会自动被加载,它就是FeignClientsConfiguration,先来看一下其定义:

@Configuration
public class FeignClientsConfiguration {

        ***部分省略***

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

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

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

        @Bean
        @Scope("prototype")
        @ConditionalOnMissingBean
        @ConditionalOnProperty(name = "feign.hystrix.enabled")
        public Feign.Builder feignHystrixBuilder() {
            return HystrixFeign.builder();
        }

    }

}

可以看到,由于@Configuration注解的缘故,该类也会在初始化时被Spring加载,同时其内部类HystrixFeignConfiguration也会被加载,只要引入了对应的类HystrixCommandHystrixFeign,而这两个类分属于com.netflix.hystrixfeign.hystrix包,这在之前的spring-cloud-starter-openfeign.pom中都有引入。另外需要注意的是feign.hystrix.enabled这个配置,只有当显式设置为true时,才会触发feignHystrixBuilder()的初始化。另外需要注意的是feignBuilder方法,当feign.hystrix.enabled不为true时,Feign.Builder会由该方法来初始化。

那么来具体看一下HystrixFeign.builder()做了什么。

  public static Builder builder() {
    return new Builder();
  }
  

这里的BuilderHystrixFeign类的内部类,继承自Feign.Builder

3 启动注解

@Slf4j
@SpringBootApplication
@EnableFeignClients
public class HuiMallOrderApplication {
    public static void main(String[] args) {
        log.info("开始启动");
        SpringApplication.run(HuiMallOrderApplication.class, args);
        log.info("启动完成");
    }
}

除了引入feign依赖之外,启动类上还需要添加注解@EnableFeignClients,那么我们还得来看看EnableFeignClients的实现:

@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 {};

}

这里要关注的是@Import(FeignClientsRegistrar.class),将FeignClientsRegistrar注册为了bean,我们来看看该类的定义

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    
    private ResourceLoader resourceLoader;
    private Environment environment;
    
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
    
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }
        
        ***部分省略***
}

该类实现了spring的3个接口ImportBeanDefinitionRegistrarResourceLoaderAwareEnvironmentAware,根据spring的实现机制,该类初始化时会调用对应的方法实现。其中setResourceLoadersetEnvironment方法实现较为简单,重点需要关注的是registerBeanDefinitions方法,该方法的入参AnnotationMetadata为我们的启动类HuiMallOrderApplication上的注解对应信息,BeanDefinitionRegistry为所有bean的注册相关信息。

接着分别来看一下registerDefaultConfigurationregisterFeignClients方法的实现:

3.1 registerDefaultConfiguration
    private void registerDefaultConfiguration(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        Map defaultAttrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

        if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
            String name;
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            }
            else {
                name = "default." + metadata.getClassName();
            }
            registerClientConfiguration(registry, name,
                    defaultAttrs.get("defaultConfiguration"));
        }
    }

metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true)方法返回EnableFeignClients类的属性列表,包括了defaultConfiguration属性,因此会进入执行if分支的内容。接着来看一下registerClientConfiguration方法:

    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
            Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(
                name + "." + FeignClientSpecification.class.getSimpleName(),
                builder.getBeanDefinition());
    }

该类主要做的事情是将@EnableFeignClients中自定义的defaultConfiguration值注册到registry中,这里注册的beanName为default+类全名,而bean定义为FeignClientSpecification的构造函数参数中增加对应的nameconfigure值。

3.2 registerFeignClients
    public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);

        Set basePackages;

        Map 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) {
            //扫描带有FeignClient注解的类
            scanner.addIncludeFilter(annotationTypeFilter);
            basePackages = getBasePackages(metadata);
        }
        else {
            final Set 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 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");
                    // 获取@FeignClient注解上的各个属性值
                    Map attributes = annotationMetadata
                            .getAnnotationAttributes(
                                    FeignClient.class.getCanonicalName());

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

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

这里的ClassPathScanningCandidateComponentProvider是Spring提供的工具,用于查找classpath下符合要求的class文件。该方法看着很长,其实做的事情就是初始化scanner并扫描指定包路径下包含@FeignClient注解的类。并将每一个类通过registerClientConfiguration方法注册到registry中。这里有两个方法需要关注一下,分别是getClientName(attributes)registerFeignClient(registry, annotationMetadata, attributes)

先看getClientName(attributes)

    private String getClientName(Map client) {
        if (client == null) {
            return null;
        }
        String value = (String) client.get("contextId");
        if (!StringUtils.hasText(value)) {
            value = (String) client.get("value");
        }
        if (!StringUtils.hasText(value)) {
            value = (String) client.get("name");
        }
        if (!StringUtils.hasText(value)) {
            value = (String) client.get("serviceId");
        }
        if (StringUtils.hasText(value)) {
            return value;
        }

        throw new IllegalStateException("Either 'name' or 'value' must be provided in @"
                + FeignClient.class.getSimpleName());
    }

该方法用于生成注册到registry的name,可以看到getClientName对name的取值优先级依次是contextId->value->name->serviceId,根据取得的name去注册registry

接着来看registerFeignClient(registry, annotationMetadata, attributes)

    private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map attributes) {
        String className = annotationMetadata.getClassName();
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientFactoryBean.class);
        validate(attributes);
        definition.addPropertyValue("url", getUrl(attributes));
        definition.addPropertyValue("path", getPath(attributes));
        String name = getName(attributes);
        definition.addPropertyValue("name", name);
        String contextId = getContextId(attributes);
        definition.addPropertyValue("contextId", contextId);
        definition.addPropertyValue("type", className);
        definition.addPropertyValue("decode404", attributes.get("decode404"));
        definition.addPropertyValue("fallback", attributes.get("fallback"));
        definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

        String alias = contextId + "FeignClient";
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

        boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
                                                                // null

        beanDefinition.setPrimary(primary);

        String qualifier = getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {
            alias = qualifier;
        }

        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                new String[] { alias });
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }

这个方法主要做的事情是从attributes中将之前@FeignClient注解上获取的属性都添加到attributes对象上,根据attributes对象生成BeanDefinitionHolder对象,这里需要关注的是definition对象的初始化是基于FeignClientFactoryBean的,该类定义为:

class FeignClientFactoryBean implements FactoryBean, InitializingBean, ApplicationContextAware 

因为实现了FactoryBean接口的关系,所以该类实现了getObject()方法,因此基于FeignClientFactoryBean创建的bean对象的注入会依赖于该方法,此处暂不做深入分析。

BeanDefinitionHolder对象创建完成后,最后调用了BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry)方法完成对象的注册,来看看该方法:

    public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {

        // Register bean definition under primary name.
        String beanName = definitionHolder.getBeanName();
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

        // Register aliases for bean name, if any.
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            for (String alias : aliases) {
                registry.registerAlias(beanName, alias);
            }
        }
    }

registerBeanDefinition方法之前已经见到过了,所以这里做的事情其实就是把生成的生成BeanDefinitionHolder对象注册到registry上,同时注册其对应的别名alias

到此feign相关的整个初始化工作也就算是完成了。

4 总结

本文梳理了feign在项目中的基本初始化流程,了解了feign中不同条件下初始化时产生的不同变化,但并未涉及基于feign进行接口调用时的源码分析,这会在后续的文章中补上。

你可能感兴趣的:(Feign初始化解析)