Feign源码深度刨析-(1)初探:@EnableFeignClients

“不积跬步,无以至千里。”

之前我们讲了ribbon的核心源码,其实在实际生产环境中,国内用ribbon+restTemplate去做微服务调用的甚少,因为会导致我们每次去调用人家一个接口,都要单独写一些代码,非常不简洁,所以我们这一个专题开始,研究一下feign的底层核心源码。

feign这个东西呢,底层也是依赖了ribbon做负载均衡的,所以是整合了ribbon的,feign的具体用法这里就不赘述了,相信来看这篇博文的,都是熟练使用的,技术,用,是很简单的,随便百度一下,csdn其他的文章,一搜一大堆,这个我就不写了,只讲核心的东西,源码。

feign里面,有一些核心的组件,需要先了解一下

  • EncoderDecoder,顾名思义,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的入口在哪里?

其实我们想一想,无非就两个地方

  1. 我们在Application启动类上面打的@EnableFeignClient注解
  2. 我们在自定义的feignClient上面打的@FeignClient注解

就先从@EnableFeignClient入手吧

@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    ......
}

两个点需要注意

  1. @Import(FeignClientsRegistrar.class),导入了一个FeignClientsRegistrar的组件
  2. 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,这里面会干什么事情,拭目以待!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值