注解 @EnableFeignClients 工作原理

概述

在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()
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值