1、什么是Feign?
这里套用Feign官方Github上的介绍:“Feign是一个灵感来自于Retrofit、JAXRS-2.0、WebSocket的Java Http客户端,Feign的主要目标是降低大家使用Http API的复杂性”。
其实,Feign底层依赖于Java的动态代理机制,对原生Java Socket或者Apache HttpClient进行封装,实现了基于Http协议的远程过程调用。当然,Feign还在此基础上实现了负载均衡、熔断等机制。
2、为什么要使用Feign?
-
声明式Http Client相对于编程式Http Client代码逻辑更加简洁,不需要处理复杂的编码请求和响应,只需要像调用本地方法即可,提高编码效率
-
集中管理Http请求方法,代码边界更加清晰
-
更好的集成负载均衡、熔断降级等功能
3、Feign依赖注入原理
使用过Feign的同学都知道,@EnableFeignClients注解是开启Fiegn功能的关键,我们通常会在该注解中添加FeignClient的所在包,以便Spring容器能够扫描到所有的FeignClient,并进行托管。后面我们便可以使用@Autowired注解自动导入了。
@SpringBootApplication @EnableFeignClients(basePackages = {"com.**.feign"}) public class Application {} 复制代码
该注解样式也是很多第三方包集成Springboot所使用的套路:一般都是开启该注解后,Springboot便可以自动装载第三方包所指定的Class,我们便可以直接使用第三方包所提供的功能,非常方便。
接下来不会详细介绍自动装载的部分,而是直接给出自动装载的主脉络,看看Spring容器到底装载了什么bean。
3.1、Feign自动装载
首先进入@EnableFeignClients源码中,查看该注解导入了什么Registrar注册器,这个注册器便是自动装载的关键。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients {...} 复制代码
从源码中可以看出,@EnableFeignClients 注解导入的是自定义的FeignClientsRegistrar类。
这种类型的注册器一般会继承Spring中的 ImportBeanDefinitionRegistrar接口,并在registerBeanDefinitions实现方法中向Spring容器注册一些bean,以达到自动注入第三方功能的目的。
// [1] 继承ResourceLoaderAware和EnvironmentAware class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // [2] 注册默认的Feign配置 registerDefaultConfiguration(metadata, registry); // [3] 注册所有定义的FeignClient registerFeignClients(metadata, registry); } }
-
[1] 从类的定义中,我们可以发现还实现了ResourceLoaderAware、EnvironmentAware两个Spring钩子接口,那么该注册类必然持有资源加载器和Spring的环境变量等信息,这个不过多叙述。
-
[2] 该方法会从@EnableFeignClients注解中提取defaultConfiguration这个key和对应的value,并把它当作默认的Feign配置注册到Spring容器中。如果没有该key,则不做任何处理。
-
[3] registerFeignClients方法会扫描@EnableFeignClients注解的basePackages,注册所有的FeignClient,下面详细介绍。
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>(); Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName()); // [1] 获取clients属性 final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients"); if (clients == null || clients.length == 0) { // [2] 如果clients属性为null,则获取basePackages属性,扫描其中的所有client ClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader(this.resourceLoader); scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class)); Set<String> basePackages = getBasePackages(metadata); for (String basePackage : basePackages) { candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); } } else { // [3] 如果clients属性不为null,则直接注入 for (Class<?> clazz : clients) { candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz)); } } // [4] 遍历所有的clients,将其封装为BeanDefinition注册进Spring容器中 for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { // ve