概述
在Spring cloud应用中,当我们要使用feign客户端时,一般要做以下三件事情 :
1.使用注解@EnableFeignClients启用feign客户端;
示例 :
@SpringBootApplication
@EnableFeignClients
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
2.使用注解@FeignClient 定义feign客户端 ;
示例 : 该例子定义了一个feign客户端,将远程服务http://test-service/test/echo映射为一个本地Java方法调用。
@FeignClient(name = "test-service", path = "/test")
public interface TestService {
@RequestMapping(value = "/echo", method = RequestMethod.GET)
TestModel echo(@RequestParam("parameter") String parameter);
}
3.使用注解@Autowired使用上面所定义feign的客户端 ;
@Autowired
TestService testService;
public void run()
{
// 这里的使用本地Java API的方式调用远程的Restful接口
TestModel dto = testService.echo("Hello,你好!");
log.info("echo : {}", dto);
}
上面的三个步骤,前两个步骤可以理解为定义feign客户端,第三步是使用所定义的feign客户端。通过调试发现,上面第三步所注入的testService是一个代理对象,如下所示 :
testService = {
$Proxy66@5502}
"HardCodedTarget(type=TestService, name=test-service, url=http://test-service/test)"
h = {
ReflectiveFeign$FeignInvocationHandler@6924}
target = {
Target$HardCodedTarget@6930}
dispatch = {
LinkedHashMap@6931} size = 1
0 = {
LinkedHashMap$Entry@6948}
"public abstract xxx.model.TestModel xxx.service.TestService.echo(java.lang.String)"
该对象会代理客户端完成远程服务方法的调用,那么,该代理对象是如何生成的 ?这篇文章,我们通过源代码分析来回答这些问题。
源代码解析
源代码版本 : spring-cloud-openfeign-core-2.1.0.RELEASE , Spring Cloud Greenwich.RELEASE
注解
@EnableFeignClients
:扫描和注册
feign
客户端
bean
定义
注解@EnableFeignClients告诉框架扫描所有使用注解@FeignClient定义的feign客户端。它又通过注解@Import导入了类FeignClientsRegistrar( feign客户端注册器),如下所示:
@EnableFeignClients
=> @Import(FeignClientsRegistrar.class)
那么 FeignClientsRegistrar 又是做什么的呢 ?我们继续。
FeignClientsRegistrar : feign
客户端注册器
FeignClientsRegistrar实现了接口 ImportBeanDefinitionRegistrar。而ImportBeanDefinitionRegistrar的设计目的,就是被某个实现类实现,配合使用@Configuration注解的使用者配置类,在配置类被处理时,用于额外注册一部分bean定义:
对于上面的例子,使用者配置类就是 TestApplication
public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing @Configuration class.
* 根据使用者配置类的注解元数据注册bean定义
* @param importingClassMetadata 使用者配置类的注解元数据
* @param registry 当前bean定义注册表,一般指当前Spring应用上下文对象,当前Spring容器
*/
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
registerBeanDefinitions – 注册feign客户端配置和feign客户端
方法FeignClientsRegistrar#registerBeanDefinitions实现如下:
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
// 注册缺省配置到容器 registry
registerDefaultConfiguration(metadata, registry);
// 注册所发现的各个 feign 客户端到到容器 registry
registerFeignClients(metadata, registry);
}
registerDefaultConfiguration– 注册feign客户端缺省配置
// 注册feign客户端的缺省配置,缺省配置信息来自注解元数据的属性 defaultConfiguration
private void registerDefaultConfiguration(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
// 获取注解@EnableFeignClients的注解属性
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
// 下面是对所注册的缺省配置的的命名,格式如下 :
// default.xxx.TestApplication
if (metadata.hasEnclosingClass()) {
// 针对注解元数据metadata对应一个内部类或者方法返回的方法本地类的情形
name = "default." + metadata.getEnclosingClassName();
}
else {
// name 举例 : default.xxx.TestApplication
// 这里 xxx.TestApplication 是注解@EnableFeignClients所在配置类的长名称
name = "default." + metadata.getClassName();
}
// 各种信息准备就绪,现在执行注册
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
#registerDefaultConfiguration方法最终注册客户端缺省配置的动作交给方法#registerClientConfiguration执行。
registerClientConfiguration – 注册feign客户端配置
// 将指定feign客户端配置configuration作为一个bean定义注册到容器:
// bean 定义对象类型 : GenericBeanDefinition
// bean class : FeignClientSpecification
// bean name : default.xxx.TestApplication.FeignClientSpecification (缺省配置)
// bean name : test-service.FeignClientSpecification (针对某个feign client 的配置)
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
// 设置构造函数参数
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
// 从bean定义构建器构造bean定义并注册到容器
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition()