OpenFeign的介绍和使用
项目地址:https://spring.io/projects/spring-cloud-openfeign
OpenFeign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用OpenFeign,可以做到使用HTTP请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问HTTP请求。
Feign 与 OpenFeign
Spring Cloud OpenFeign 并不是独立的技术。它底层基于 Netflix Feign,Netflix Feign 是 Netflix 设计的开源的声明式 WebService 客户端,用于简化服务间通信。Netflix Feign 采用“接口+注解”的方式开发,通过模仿 RPC 的客户端与服务器模式(CS),采用接口方式开发来屏蔽网络通信的细节。OpenFeign 则是在 Netflix Feign 的基础上进行封装,结合原有 Spring MVC 的注解,对 Spring Cloud 微服务通信提供了良好的支持。使用 OpenFeign 开发的方式与开发 Spring MVC Controller 颇为相似。
OpenFeign的使用
服务提供者
第一步,利用 Spring Initializr 向导创建 warehouse-service 工程。
1 2 3 4 5 6 7 8 9 10 11 12 13
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
|
第二步,编辑 application.yml,配置nacos服务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| spring: application: name: warehouse-service cloud: nacos: discovery: username: nacos password: nacos server-addr: 47.106.172.60:8848 namespace: public
server: port: 9010
|
第三步,创建 Stock 库存类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
@Data public class Stock { private Long skuId; private String title; private Integer quantity; private String unit; private String description;
public Stock(Long skuId, String title, Integer quantity, String unit) { this.skuId = skuId; this.title = title; this.quantity = quantity; this.unit = unit; } }
|
第四步,创建仓储服务控制器 WarehouseController。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @RestController public class WarehouseController {
@GetMapping("/stock") public Stock getStock(Long skuId){ Map result = new HashMap(); Stock stock = null; if(skuId == 1101l){ stock = new Stock(1101l, "Apple iPhone 11 128GB 紫色", 32, "台"); stock.setDescription("Apple 11 紫色版对应商品描述"); }else if(skuId == 1102l){ stock = new Stock(1101l, "Apple iPhone 11 256GB 白色", 0, "台"); stock.setDescription("Apple 11 白色版对应商品描述"); }else{ } return stock; } }
|
第五步,启动服务,将服务 warehouse-service
注册到Nacos。

服务消费者
第一步,利用 Spring Initializr 创建 order-service 工程,引入以下依赖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
|
第二步,启用 OpenFeign 需要在应用入口 OrderServiceApplication 增加 @EnableFeignClients 注解,其含义为通知 Spring 启用 OpenFeign 声明式通信。
1 2 3 4 5 6 7 8
| @SpringBootApplication @EnableFeignClients public class OrderServiceApplication {
public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }
|
第三步,默认 OpenFeign 并不需要任何配置,在 application.yml 配置nacos服务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| spring: application: name: order-service cloud: nacos: discovery: username: nacos password: nacos server-addr: 47.106.172.60:8848 namespace: public
server: port: 8081
|
第四步,创建OpenFeign的通信接口与响应对象。
1 2 3 4 5
| @FeignClient("warehouse-service") public interface WarehouseFeignClient { @GetMapping("/stock") Stock getStock(@RequestParam("skuId") Long skuId); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@Data @ToString public class Stock { private Long skuId; private String title; private Integer quantity; private String unit;
}
|
OpenFeign 通过“接口+注解”形式描述数据传输逻辑,并不需要程序员编写具体实现代码便能实现服务间高可用通信。
- @FeignClient 注解说明当前接口为 OpenFeign 通信客户端,参数值 warehouse-service 为服务提供者 ID,这一项必须与 Nacos 注册 ID 保持一致。在 OpenFeign 发送请求前会自动在 Nacos 查询 warehouse-service 所有可用实例信息,再通过内置的 Ribbon 负载均衡选择一个实例发起 RESTful 请求,进而保证通信高可用。
- 声明的方法结构,接口中定义的方法通常与服务提供者的方法定义保持一致。这里有个非常重要的细节:用于接收数据的 Stock 对象并不强制要求与提供者端 Stock 对象完全相同,消费者端的 Stock 类可以根据业务需要删减属性,但属性必须要与提供者响应的 JSON 属性保持一致,在 OpenFeign 获取响应后便根据 JSON 属性名自动反序列化到 Stock 对象中。
1 2 3 4
| 1.在第一次访问 WarehouseFeignClient 接口时,Spring 自动生成接口的实现类并实例化对象。 2.当调用 getStock() 方法时,Ribbon 获取 warehouse-service 可用实例信息,根据负载均衡策略选择合适实例。 3.OpenFeign 根据方法上注解描述的映射关系生成完整的 URL 并发送 HTTP 请求,如果请求方法是 @PostMapping,则参数会附加在请求体中进行发送。 4.warehouse-service 处理完毕返回 JSON 数据,消费者端 OpenFeign 接收 JSON 的同时反序列化到 Stock 对象,并将该对象返回。
|
如何更改 OpenFeign 默认的负载均衡策略
服务消费者 application.yml 文件增加指定服务提供者的负载均衡配置信息。
1 2 3 4
| warehouse-service: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
|
为何通过yml文件能配置负载均衡策略
关键类:RibbonClientConfiguration
1 2 3 4 5 6 7 8 9 10
| @Bean @ConditionalOnMissingBean public IRule ribbonRule(IClientConfig config) { if (this.propertiesFactory.isSet(IRule.class, name)) { return this.propertiesFactory.get(IRule.class, config, name); } ZoneAvoidanceRule rule = new ZoneAvoidanceRule(); rule.initWithNiwsConfig(config); return rule; }
|
是否设置负载均衡规则IRule
1 2 3
| public boolean isSet(Class clazz, String name) { return StringUtils.hasText(getClassName(clazz, name)); }
|
判断规则就是应用环境是否设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
public String getClassName(Class clazz, String name) { if (this.classToProperty.containsKey(clazz)) { String classNameProperty = this.classToProperty.get(clazz); String className = environment .getProperty(name + "." + NAMESPACE + "." + classNameProperty); return className; } return null; }
|
classToProperty是什么呢
1 2 3 4 5 6 7
| public PropertiesFactory() { classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName"); classToProperty.put(IPing.class, "NFLoadBalancerPingClassName"); classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName"); classToProperty.put(ServerList.class, "NIWSServerListClassName"); classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName"); }
|
通过如果配置存则获取负载均衡规则
1
| return this.propertiesFactory.get(IRule.class, config, name)
|
反射实例化yml配置的负载均衡规则类实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public <C> C get(Class<C> clazz, IClientConfig config, String name) { String className = getClassName(clazz, name); if (StringUtils.hasText(className)) { try { Class<?> toInstantiate = Class.forName(className); return (C) SpringClientFactory.instantiateWithConfig(toInstantiate, config); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Unknown class to load " + className + " for class " + clazz + " named " + name); } } return null; }
|
开启默认的 OpenFeign 数据压缩功能
在 OpenFeign 中,默认并没有开启数据压缩功能。但如果你在服务间单次传递数据超过 1K 字节,强烈推荐开启数据压缩功能。默认 OpenFeign 使用 Gzip 方式压缩数据,对于大文本通常压缩后尺寸只相当于原始数据的 10%~30%,这会极大提高带宽利用率。但有一种情况除外,如果应用属于计算密集型,CPU 负载长期超过 70%,因数据压缩、解压缩都需要 CPU 运算,开启数据压缩功能反而会给 CPU 增加额外负担,导致系统性能降低,这是不可取的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| feign: compression: request: enabled: true mime-types: text/xml, application/xml, application/json min-request-size: 1024 response: enabled: true
|
替换默认通信组件
OpenFeign 默认使用 Java 自带的 URLConnection 对象创建 HTTP 请求,但接入生产时,如果能将底层通信组件更换为 Apache HttpClient、OKHttp 这样的专用通信组件,基于这些组件自带的连接池,可以更好地对 HTTP 连接对象进行重用与管理。作为 OpenFeign 目前默认支持 Apache HttpClient 与 OKHttp 两款产品。
第一步、引入 feign-okhttp 依赖包。
1 2 3 4 5
| <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> <version>11.0</version> </dependency>
|
第二步、配置OkHttpClient 对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Bean public okhttp3.OkHttpClient okHttpClient(){ return new okhttp3.OkHttpClient.Builder() .readTimeout(10, TimeUnit.SECONDS) .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .connectionPool(new ConnectionPool()) .build(); }
|
第三步、在 application.yml 中启用 OkHttp。
1 2 3
| feign: okhttp: enabled: true
|