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.0org.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 = "https://www.it610.com/article/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 = https://www.it610.com/article/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)
【Feign初始化解析】先看getClientName(attributes)
private String getClientName(Map client) { if (client == null) { return null; } String value = https://www.it610.com/article/(String) client.get("contextId"); if (!StringUtils.hasText(value)) { value = https://www.it610.com/article/(String) client.get("value"); } if (!StringUtils.hasText(value)) { value = https://www.it610.com/article/(String) client.get("name"); } if (!StringUtils.hasText(value)) { value = https://www.it610.com/article/(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 // nullbeanDefinition.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进行接口调用时的源码分析,这会在后续的文章中补上。

    推荐阅读