“不积跬步,无以至千里。”
前面说了,@EnableFeignClients 这个注解往容器中导入了一个组件 FeignClientsRegistrar ,这个组件实现了 ImportBeanDefinitionRegistrar接口,那么它的 registerBeanDefinitions() 方法就会往spring容器中导入一些组件
registerFeignClients(metadata, registry);
这个方法会搞一个组件扫描器ClassPathScanningCandidateComponentProvider
,专门扫描我们自己写的Feign客户端的接口,即添加了@FeignClient
注解的接口
扫描之后,会放进一个Set集合中,然后遍历这个集合,依次调用registerFeignClient()
方法将其注册到spring容器中
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> 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);
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 = name + "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);
}
可以看到,方法并不长
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
上来就看到一段极为特别的代码,FeignClientFactoryBean
,这是一个工厂Bean,在Spring中是比较特殊的一个组件,相当于把扫描出来的我们自己写的那些打上了@FeignClient注解的接口,全部都注册为了一个工厂Bean,后续使用的时候直接调用getObject()
方法即可,你觉得这个方法会在什么时候被调用呢?当然是给Controller组件DI(依赖注入)的时候!!!这个我们后续会去验证
然后下面的一系列addXXX()
方法,就是读取@FeignClient
注解里面的属性放进Bean的定义信息里面的一个过程
比如 definition.addPropertyValue(“name”, name); 就是把服务名称给进bean的定义信息里面去
然后 getBeanDefinition()
,成功拿到一个 AbstractBeanDefinition
,其实没啥好说的,spring基操,这里面就包含了服务名称等我们配置在 @FeignClient 注解里面的参数
最后封装成一个 BeanDefinitionHolder
,调用一个spring的工具类给注册到容器中,BeanName
就是类全限定名。
后续Controller中,需要使用Feign客户端的时候,只需要声明一个接口类即可,再使用@Autowired注解,就会把动态代理注入到Controller的属性中,这个叫DI,依赖注入,因此在实例化Controller组件的时候会去调用我们这个FeignClientFactoryBean
的getObject()
方法,获取代理对象
@Override
public Object getObject() throws Exception {
return getTarget();
}
getTarget(),获取代理对象的逻辑都在这个方法里面
/**
* @param <T> the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context information
*/
<T> T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
@param the target type of the Feign client,返回值类型就是FeignClient的类型,这是肯定的,不然没法赋值给Controller的属性
FeignContext context = applicationContext.getBean(FeignContext.class);
这行代码啥意思,还记得之前写ribbon的时候有说过,一个被调服务对应一个spring容器,容器里面包含了那个服务的自己的组件,ILoadBalancer、IRule等等,这里也是一样的,每个FeignContext
也是代表一个被调服务的独立容器,里面包含自己的一些组件,比如编码器解码器,日志组件,注解解释器等等
然后下面调用了一个feign()
方法拿到一个Feign.Builder
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
configureFeign(context, builder);
return builder;
}
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
这里就是从被调服务对应的spring容器中获取属于自己的 FeignLoggerFactory
组件
protected <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(this.name, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " + type + " for "
+ this.name);
}
return instance;
}
instance
就是当前服务对应的spring容器里面的FeignLoggerFactory
组件,而getInstance()
方法就是从容器中那组件,this.name就是服务名称,type就是FeignLoggerFactory.class,即组件的类型
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
return context.getBean(type);
}
return null;
}
接着走, Logger logger = loggerFactory.create(this.type);
拿到一个Logger
组件,就是用来记录feign调用过程中产生的日志的,默认使用的是Slf4jLogger
Feign.Builder builder = get(context, Feign.Builder.class)
然后从spring容器中获取一个Feign.Builder组件,这是比较关键的一行代码,这个组件肯定也是通过自动配置类去注入到容器中的
在 org.springframework.cloud.openfeign 这个包下面找到了一个 FeignClientsConfiguration
,发现就是说这里面配置了大量的组件,就包括了这个Feign.Builder
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
会发现说,这里找到了两个Builder,一个是HystrixFeign,这个很明显是跟Hystrix整合的时候使用的
另外一个就是默认的Builder了,只不过整合了一个retryer
,就是一个重试的机制,默认使用的是这个,
除非你在配置文件中有feign.hystrix.enabled
相关的配置
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
接着分别把logger、encoder、decoder、contract组件注入到这个Feign.Builder中
当然也都在从这个被调服务对应的spring容器中获取的
logger默认使用的是Slf4jLogger
encoder默认使用的是SpringEncoder
decoder默认使用的是ResponseEntityDecoder
contract默认使用的是SpringMvcContract
全部都是在FeignClientsConfiguration
这个配置类配置了,之前我们在feign的初探章节给大家说了默认组件,这次再带着源码看一下,验证我们的结论,我写的博文,每一篇,每一字,全都的结论都是来源于源码,源码见真知!平时遇到问题,也尽量有能力的话,去翻翻源码,不要“面向百度编程”,不太好,培养不出来能力
@Bean
@ConditionalOnMissingBean(FeignLoggerFactory.class)
public FeignLoggerFactory feignLoggerFactory() {
return new DefaultFeignLoggerFactory(logger);
}
@Override
public Logger create(Class<?> type) {
return this.logger != null ? this.logger : new Slf4jLogger(type);
}
@Bean
@ConditionalOnMissingBean
public Encoder feignEncoder() {
return new SpringEncoder(this.messageConverters);
}
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
ok,总结一下,扫描器扫描出来我们打了@FeignClient注解的接口,会把他们注册为l类型为FeignClientFactoryBean
的工厂Bean,然后在适当的时候(实例化Controller组件的时候,DI)调用getObject()
方法获取,这篇文章主要在说FeignClientFactoryBean
这个组件怎么获取一个FeignClient的动态代理,我控制一下篇幅,不要太长,不然容易阅读疲劳,下一篇文章继续讲动态代理的构建流程。
最后,也是稍微多说一点,其实写这种源码分析的博客,说简单很简单,按照自己的思路,胡乱的粘贴一些东西,写出来,也花不了多少时间,但最后写完,仅自己看得懂,也许同样深入研究过那个源码的大佬,看得懂,对于大多数朋友是不友好的,也得不到反馈
想要真正写的很透彻,高质量的文章,也没那么容易
首先,需要深入理解这个技术的核心源码,要做到全局性的把握
其次,要学会抓大放小,源码里面细枝末节的东西太多,什么都写,没有深度,要有自己先过滤一下,把重点的东西展示出来,还要分析的深度恰如其分,还不能分析的过于的细节,容易让读者一头雾水,抓不住重点;
最后,还要把难点写明白,自己理解是一码事,让别人看懂是一码事,需要反复审稿,尽量做到通俗易懂。