“不积跬步,无以至千里。”
之前我们讲了ribbon的核心源码,其实在实际生产环境中,国内用ribbon+restTemplate去做微服务调用的甚少,因为会导致我们每次去调用人家一个接口,都要单独写一些代码,非常不简洁,所以我们这一个专题开始,研究一下feign的底层核心源码。
feign这个东西呢,底层也是依赖了ribbon做负载均衡的,所以是整合了ribbon的,feign的具体用法这里就不赘述了,相信来看这篇博文的,都是熟练使用的,技术,用,是很简单的,随便百度一下,csdn其他的文章,一搜一大堆,这个我就不写了,只讲核心的东西,源码。
feign里面,有一些核心的组件,需要先了解一下
-
Encoder和Decoder,顾名思义,feign的编码器和解码器,调用接口的时候,如果传递的参数是对象,feign会通过Encoder组件进行编码,把对象进行json序列化,转成一个json串,默认使用ResponseEntityDecoder;另一头,你收到了一个json以后,再通过Decoder进行解码,转换成一个本地的对象,默认使用SpringEncoder
-
Logger,日志组件,feign是负责接口调用,发送http请求的,所以feign是可以打印这个接口调用请求的日志的,默认使用Slf4jLogger
-
Contract,你可以理解成spring mvc的注解解释器,使用feign的时候,方法的形参通常会使用类似@PathVariable、@RequestMapping等spring web mvc的注解,feign使用这个组件来解释注解,默认使用SpringMvcContract
-
FeignClient,feign核心入口,里面包含了一系列的组件,比如Encoder、Decoder、Logger、Contract等,与ribbon整合时使用LoadBalancerFeignClient
-
Feign.Builder,FeignClient的一个实例构造器,使用构造器模式来构造一个FeignClient,与hystrix整合时使用HystrixFeign.Builder
开始源码刨析之前,要明白一件事情,feign的入口在哪里?
其实我们想一想,无非就两个地方
- 我们在Application启动类上面打的
@EnableFeignClient
注解 - 我们在自定义的feignClient上面打的
@FeignClient
注解
就先从@EnableFeignClient
入手吧
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
......
}
两个点需要注意
- @Import(FeignClientsRegistrar.class),导入了一个
FeignClientsRegistrar
的组件 - Scans for interfaces that declare they are feign clients(via {@link FeignClient
@FeignClient
}),这个注解的注释提到“扫描那些标注了@FeignClient的feign客户端接口”
根据这些线索,我们基本判断,feign这里,有一个核心的机制,来扫描所有打上了@FeignClient注解的接口,然后推测一下,会对这个接口实现一个动态代理,并解析接口方法上标注的spring mvc注解,最终生成一个http请求,使用底层的通信组件整合ribbon,选择一个server,发送请求,接受响应并返回… …
当然,上面的内容只是个人的猜想,都源码的一些经验之谈,毕竟我们只是声明了一个feignclient的接口,就直接在controller中使用@Autowired注入使用了,并没有去手动注入此类型的Bean,猜想,不是瞎想,一定是有凭有据
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
}
FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar
接口,并实现了registerBeanDefinitions()方法!!!
了解spring的都知道,这是往spring容器中导入组件的一种方式
registerDefaultConfiguration(metadata, registry);
注册默认的配置,这个应该是解析一些上面提到的默认组件到spring容器里面去
registerFeignClients(metadata, registry);
注册FeignClients!!!这个极有可能就是扫描各个包下面的@FeignClient注解,然后生成@FeignClient的动态代理,注册这些@FeignClient到spring容器中,这样controller中才能自动注入Bean
先来看看第一个方法 registerDefaultConfiguration
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map<String, Object> 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"));
}
}
首先去解析EnableFeignClients,拿到注解的属性,然后判断一下
defaultAttrs != null && defaultAttrs.containsKey(“defaultConfiguration”)
这个表达式肯定是返回true
的,因为即使我们没有配置属性,还是有默认值的
name = “default.” + metadata.getClassName();
拿到一个name,就是启动类的全限定类名,然后前面拼接一个“default”,类似
default.com.wc.resttemplate.Springboot01Application 这样的一个字符串
defaultAttrs.get(“defaultConfiguration”) 这个东西其实是空的,因为我们没有配置这个属性
然后调用了一个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());
}
这里面其实没什么核心的东西,搞了一个BeanDefinitionBuilder
,把name(“default+启动类全限定类名”)和configuration(空的)传进去作为一个bean的构造参数,可能接下来要用 BeanDefinitionBuilder 构造某个bean,构造这个bean的时候,需要往构造函数中传入两个入参,就是我们上面解析出来的 name 和 defaultConfiguration
这个 name + “.” + FeignClientSpecification.class.getSimpleName() ,
就是 default.com.wc.resttemplate.Springboot01Application.FeignClientSpecification 的一串东东
然后使用 registry.registerBeanDefinition(…) 把上面一串东东作为bean name注册到容器中
ok,看完了第一个方法 registerDefaultConfiguration,好像也没搞出来什么名堂,无非就是注册了一个奇怪的Bean,别的也没啥了,其实这个方法比较简单,不是太核心
下一讲来看第二个方法
registerFeignClients(metadata, registry);
注册feign clients,这里面会干什么事情,拭目以待!