参考资料:
Spring Cloud 官网
相关版本:Spring Boot 2.1.5 、 spring cloud Greenwich.SR1
spring-cloud-openfeign 2.1.1
Spring Cloud Feign 默认使用 HTTP 的形式进行远程服务调用。
先来看一个简单的案例,假设此时有一个服务provider 和一个服务consumer。
服务provider | 9527 | 提供接口/hello/{name} |
服务consumer | 9528 | 调用 服务provider的 hello/{name} 接口 |
服务consumer 通过 RestTemplate 发起 HTTP 请求调用服务 provider 接口代码如下:
@RequestMapping("/feign/hello/{param}")
public Object hello(@PathVariable String param,) throws InterruptedException {
return restTemplate.getForObject("http://localhost:9527/hello/" + param,String.class);
}
在上面代码中,我们通过传入一个字符参数,发起 http 请求,接收一个字符串的返回结果。
假设,此时我们需要传入的是一个对象,对象中存在多个参数,返回结果同样也是一个对象。
这时候,为了能够提高开发效率,我们就需要对 RestTemplate 进行封装,例如,通过反射获取参数对象值,并进行拼接,返回结果同样需要通过反射进行赋值等。
而 Feign 帮我们对 RestTemplate 进行了封装,除此之外,Feign 集成了 Hystrix ,Ribbon 等功能。在之后会针对 Feign 对 Hystrix 和 Ribbon 等集成进行分析。
Feign 简单例子
**step1、**引入依赖
**step2、**开启 Feign 功能
@EnableFeignClients
**step3、**编写相关代码,如下图
总结:使用 Feign 进行远程调用,只需定义一个 Feign 客户端,通过调用方法的形式,便可完成远程接口调用。
Feign Client 如何被加载到 Spring 中
在上面 Feign 调用的案例中 HelloClient 只需 @FeignClient 注解,然后 @Autowired 进行注入便可完成 Feign Client 注入。
下面通过源码分析,看看 Feign Client 是如何被加载到 Spring 中的。
Feign Client BeanDefinition 信息注册
以 @EnableFeignClients 为入口,进行源码分析
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}
通过 EnableFeignClients 源码查看,聚焦 @Import(FeignClientsRegistrar.class) 中的 FeignClientsRegistrar.class。
FeignClientsRegistrar
FeignClientsRegistrar 类实现了 ImportBeanDefinitionRegistrar 。聚焦 FeignClientsRegistrar#registerBeanDefinitions 方法。
知识点:实现了 ImportBeanDefinitionRegistrar 的类,会根据该类的配置属性进行相关 Bean 的动态注入。
FeignClientsRegistrar#registerBeanDefinitions
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 注册 EnableFeignClients 中配置的相关到 BeanDefinitionRegistry 中
registerDefaultConfiguration(metadata, registry);
// 注册 Feign 客户端到 BeanDefinitionRegistry 中
registerFeignClients(metadata, registry);
}
}
方法中调用了两个方法
-
registerDefaultConfiguration(metadata, registry);
该方法主要是获取注解 @EnableFeignClients 中是否有相关的配置信息,如果有进行配置信息的注册 -
registerFeignClients(metadata, registry);
该方法是用来将我们定义的带有 @FeignClient 注解的类注册到 BeanDefinitionRegistry 中
追踪主线方法 registerFeignClients(metadata, registry);
FeignClientsRegistra#registerFeignClientsr
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 定义过滤条件,需要包含 FeignClient
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
scanner.addIncludeFilter(annotationTypeFilter);
// 获取扫描的包
basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
// 根据包路径,过滤条件,获取相关类的 BeanDefinition
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// 如果每个 FeignClient 有相关的配置,则进行配置注入
registerClientConfiguration(registry, name,attributes.get("configuration"));
// 进行 Feign Client 注入
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
}
处理逻辑
1、获取所有需要扫描的包路径。
该路径可以从 EnableFeignClients 中的配置中获取,如:value,basePackages,basePackageClasses。
如果 EnableFeignClients 中没有进行配置,默认扫描包路径为:应用 @EnableFeignClients 的类,也就是 Application 启动类所在包路径为主。
例如:com.qguofeng.Application.class 启动类 中 @EnableFeignClients 中没有相关扫描包配置时,则默认扫描 FeignClient 的路径为:com.qguofeng
2、增加扫描条件
在 步骤 1 中确定了需要扫描的包路径,为了获取精确数据,增加筛选条件: