目录
一、背景
@FeignClient是SpringCloud中用于声明一个Feign客户端的注解,用于解决模块方法互相调用的问题,Feign是一个声明式的WebService客户端,通过Feign,只需要创建一个接口,并使用注解来描述请求,就可以直接执行HTTP请求了。
二、@FeignClient介绍
@FeignClient 是 Spring Cloud 中用于声明一个 Feign 客户端的注解。由于SpringCloud采用分布式微服务架构,难免在各个子模块下存在模块方法互相调用的情况。
比如订单服务要调用库存服务的方法,@FeignClient()注解就是为了解决这个问题的。
Feign 是一个声明式的 Web Service 客户端,它的目的是让编写 HTTP 客户端变得更简单。通过 Feign,只需要创建一个接口,并使用注解来描述请求,就可以直接执行 HTTP 请求了。
@FeignClient()注解的源码要求它必须在Interface接口上使用( FeignClient注解被@Target(ElementType.TYPE)修饰,表示FeignClient注解的作用目标在接口上)
SpringBoot服务的启动类必须要有@EnableFeignClients 注解才能使@FeginClient注解生效。
三、@FeignClient工作原理及整体流程
Feign服务调用的工作原理可以总结为以下几个步骤
- 首先通过@EnableFeignCleints注解开启FeignCleint。
- 根据Feign的规则实现接口,添加@FeignCleint注解。程序启动后,会扫描所有有@FeignCleint的类,并将这些信息注入到ioc容器中。
- 注入时从FeignClientFactoryBean.class获取FeignClient。
- 当接口的方法被调用时,通过jdk的代理,来生成具体的RequesTemplate,RequesTemplate生成http的Request。
- Request交给Client去处理,其中Client可以是HttpUrlConnection、HttpClient也可以是Okhttp。
- Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。
整体流程:

四、@FeignClient常用属性
1.name、value
指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
这两个属性的作用是一样的,如果没有配置url,那么配置的值将作为服务的名称,用于服务的发现,反之只是一个名称。
@FeignClient(name = "order-server")
public interface OrderRemoteClient {
@GetMapping("/order/detail")
public Order detail(@RequestParam("orderId") String orderId);
}
注意:
- 这里写的是你要调用的那个服务的名称(
spring.application.name属性配置),而不是你自己的那个服务的名称。 - 如果同一个工程中出现两个接口使用一样的服务名称会报错。原因是Client名字注册到容器中重复了。除非指定不同的
contextId参数。
两种解决方案:
- 增加配置 spring.main.allow-bean-definition-overriding=true
- 为每个FeignClient手动指定不同的contextId
2.contextId
比如我们有个user服务,但user服务中有很多个接口,我们不想将所有的调用接口都定义在一个类中,那就可以给不同的client指定contextId,不然就会报异常。
注意:contextId不能带_等符号。
@FeignClient(name = "order-server")
public interface OrderRemoteClient {
@GetMapping("/api/order/detail", contextId = "OrderRemoteClient")
public Order detail(@RequestParam("orderId") String orderId);
}
上面给出了Bean名称冲突后的解决方案,下面来分析下contextId在Feign Client的作用,在注册Feign Client Configuration的时候需要一个名称,名称是通过getClientName方法获取的:
String name = getClientName(attributes);
registerClientConfiguration(registry, name, attributes.get("configuration"));
private String getClientName(Map<String, Object> client) {
if (client == null) {
return null;
}
String value = (String) client.get("contextId");
if (!StringUtils.hasText(value)) {
value = (String) client.get("
发表评论