目录
前言
OpenFeign 介绍
OpenFeign 的前⾝
Spring Cloud Feign
快速上⼿
引⼊依赖
添加注解
编写 OpenFeign 的客户端
远程调⽤
OpenFeign 参数传递
传递单个参数
传递多个参数
传递对象
传递 JSON
最佳实践
Feign 继承⽅式
创建⼀个 Module
引⼊依赖
编写接⼝
打Jar包
服务提供⽅
Feign 抽取⽅式
创建⼀个 module
引⼊依赖
编写 API
打 Jar 包
服务消费⽅使⽤ product-api
指定扫描类: ProductApi
从本地读取Jar包
前言
微服务之间的通信⽅式,通常有两种: RPC 和 HTTP. 在 SpringCloud 中,默认是使⽤ HTTP 来进⾏微服务的通信,最常⽤的实现形式有两种:
• RestTemplate
• OpenFeign
RPC(Remote Procedure Call)远程过程调⽤,是⼀种通过⽹络从远程计算机上请求服务,⽽不需要了解底层⽹络通信细节。RPC 可以使⽤多种⽹络协议进⾏通信,如 HTTP、TCP、UDP等,并且在 TCP/IP⽹络四层模型中跨越了传输层和应⽤层。简⾔之 RPC 就是像调⽤本地⽅法⼀样调⽤远程⽅法。常⻅的 RPC 框架有:
1. Dubbo:Apache Dubbo
2. Thrift :Apache Thrift - Home
3. gRPC:gRPC
OpenFeign 介绍
OpenFeign 是⼀个声明式的 Web Service 客户端.它让微服务之间的调⽤变得更简单,类似controller 调⽤ service ,只需要创建⼀个接⼝,然后添加注解即可使⽤ OpenFeign.
OpenFeign 的前⾝
Feign 是 Netflix 公司开源的⼀个组件.
• 2013年6⽉,Netflix 发布 Feign 的第⼀个版本1.0.0
• 2016年7⽉, Netflix 发布 Feign 的最后⼀个版本 8.18.0 2016年,Netflix将Feign捐献给社区
• 2016年7⽉OpenFeign的⾸个版本9.0.0 发布,之后⼀直持续发布到现在.可以简单理解为 Netflix Feign 是 OpenFeign 的祖先,或者说 OpenFeign 是 Netflix Feign 的升级版. OpenFeign 是 Feign 的⼀个更强⼤更灵活的实现.
Spring Cloud Feign
Spring Cloud Feign 是 Spring 对 Feign 的封装, 将 Feign 项⽬集成到 Spring Cloud ⽣态系统中.受 Feign 更名影响,Spring Cloud Feign也有两个 starter
• spring-cloud-starter-feign
• spring-cloud-starter-openfeign 由于Feign 的停更维护,对应的,我们使⽤的依赖是spring-cloud-starter-openfeign
OpenFeign 官⽅⽂档:GitHub - OpenFeign/feign: Feign makes writing java http clients easier
Spring Cloud Feign⽂档:Spring Cloud OpenFeign
快速上⼿
引⼊依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
添加注解
在 order-service 的启动类添加注解 @EnableFeignClients ,开启 OpenFeign 的功能.
@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication {public static void main(String[] args) {SpringApplication.run(OrderServiceApplication.class, args);}
}
编写 OpenFeign 的客户端
基于 SpringMVC 的注解来声明远程调⽤的信息
package com.bite.order.api;import com.bite.order.model.ProductInfo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;/*** Created with IntelliJ IDEA.* Description:* User: wuyulin* Date: 2024-09-22* Time: 0:44*/
@FeignClient(name = "product-service",path = "/product")
public interface ProductApi {@RequestMapping("/info/{productId}")ProductInfo getProductInfoById(@PathVariable("productId") Integer productId);
}
@FeignClient 注解作⽤在接⼝上
参数说明:
• name/value(指定想访问的服务,设置服务的 IP 地址和端口号):指定 FeignClient 的名称,也就是微服务的名称,⽤于服务发现,Feign 底层会使⽤ Spring Cloud LoadBalance进⾏负载均衡.也可以使⽤ url 属性指定⼀个具体的url.
• path(path 和 @RequestMapping 组成了接口的路径):定义当前 FeignClient 的统⼀前缀
远程调⽤
设置好 ProductApi 接口后,通过 ProductApi 的实例调用 getProductInfoById 方法就可以将请求发送给 product-service服务 /product/info/{productId} 路径的接口,并将请求返回的结果赋值给 ProductInfo 类型的对象
package com.bite.order.service;import com.bite.order.api.ProductApi;
import com.bite.order.mapper.OrderMapper;
import com.bite.order.model.OrderInfo;
import com.bite.order.model.ProductInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate ProductApi productApi;public OrderInfo selectOrderById(Integer orderId){OrderInfo orderInfo = orderMapper.selectOrderById(orderId);ProductInfo productInfo = productApi.getProductInfoById(orderInfo.getProductId());orderInfo.setProductInfo(productInfo);return orderInfo;}
}
通过上述代码可以看出,通过 OpenFeign 来远程调用其他微服务的接口就像调用本地方法一样,非常的优雅。
OpenFeign 参数传递
通过观察,我们也可以发现,Feign 的客户端和服务提供者的接⼝声明⾮常相似上⾯例⼦中,演⽰了 Feign 从URL中获取参数,接下来演⽰下 Feign 参数传递的其他⽅式
传递单个参数
服务提供⽅ product-service
@RequestMapping("/product")
@RestController
public class ProductController {@RequestMapping("/p1")public String p1(Integer id){return "p1接收到参数:"+id;}
}
Feign 客户端
通过 OpenFeign 发送的 http 请求的参数要放到 query string 中,加上注解 @RequestParam 标注
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {@RequestMapping("/p1")String p1(@RequestParam("id") Integer id);
}
注意:@RequestParam 做参数绑定, 不能省略
服务消费⽅ order-service
@RequestMapping("/feign")
@RestController
public class TestFeignController {@Autowiredprivate ProductApi productApi;@RequestMapping("/o1")public String o1(Integer id){return productApi.p1(id);}
}
自我理解:在 product-service 上有一些接口,order-service 需要访问,按照 OpenFeign 的要求设置好接口,设置了要访问接口的 IP 地址,端口号,路径,由于参数要设置在 HTTP 请求的 query string 处,所以接口的参数前要加上:@RequestParam 注解,OpenFeign 就会在构造 http 请求时将参数放到 query string 处。
传递多个参数
使⽤多个 @RequestParam 进⾏参数绑定即可
服务提供⽅product-service
@RequestMapping("/p2")
public String p2(Integer id,String name){return "p2接收到参数,id:"+id +",name:"+name;
}
Feign 客户端
@RequestMapping("/p2")
String p2(@RequestParam("id")Integer id,@RequestParam("name")String name);
服务消费⽅ order-service
@RequestMapping("/o2")
public String o2(@RequestParam("id")Integer id,@RequestParam("name")String
name){return productApi.p2(id,name);
}
传递对象
服务提供⽅ product-service
@RequestMapping("/p3")
public String p3(ProductInfo productInfo){return "接收到对象, productInfo:"+productInfo;
}
Feign 客户端
使用 @SpringQueryMap 将商品信息放到 http 请求的 query string 中发送给接口
@RequestMapping("/p3")
String p3(@SpringQueryMap ProductInfo productInfo);
服务消费⽅order-service
@RequestMapping("/o3")
public String o3(ProductInfo productInfo){return productApi.p3(productInfo);
}
传递 JSON
服务提供⽅ product-service
加上 @RequestBody 注解表示要接收 http 请求 body 中 json 格式的内容
@RequestMapping("/p4")
public String p4(@RequestBody ProductInfo productInfo){return "接收到对象, productInfo:"+productInfo;
}
Feign 客户端
加上 @RequestBody 注解表示将 productInfo 的信息以 json 格式放到 body 中
@RequestMapping("/p4")
String p4( ProductInfo productInfo);
服务消费⽅ order-service
@RequestMapping("/o4")
public String o4(@RequestBody ProductInfo productInfo){System.out.println(productInfo.toString());return productApi.p4(productInfo);
}
最佳实践
最佳实践,其实也就是经过历史的迭代,在项⽬中的实践过程中,总结出来的最好的使⽤⽅式.通过观察,我们也能看出来,Feign 的客户端与服务提供者的 controller 代码⾮常相似
Feign 客户端
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {@RequestMapping("/{productId}")ProductInfo getProductById(@PathVariable("productId") Integer productId);
}
服务提供⽅ Controller
@RequestMapping("/product")
@RestController
public class ProductController {@RequestMapping("/{productId}")public ProductInfo getProductById(@PathVariable("productId") Integer productId){//...}
}
有没有⼀种⽅法可以简化这种写法呢?
Feign 继承⽅式
Feign ⽀持继承的⽅式,我们可以把⼀些常⻅的操作封装到接⼝⾥.我们可以定义好⼀个接⼝,服务提供⽅实现这个接⼝,服务消费⽅编写 Feign 接⼝的时候, 直接继承这个接⼝
创建⼀个 Module
接⼝可以放在⼀个公共的Jar包⾥,供服务提供⽅和服务消费⽅使⽤
引⼊依赖
引入 spring boot 和 open feign 的依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
</dependencies>
编写接⼝
在 product-api 中编写接口 ProductInterface
public interface ProductInterface {@RequestMapping("/info/{productId}")ProductInfo getProductInfoById(@PathVariable("productId") Integer productId);@RequestMapping("/oneParmeter")String oneParmeter(@RequestParam("id") Integer id);@RequestMapping("/getMoreParmeter")String moreParmeter(@RequestParam("id") Integer id,@RequestParam("name")String name);@RequestMapping("/getObject")String getObject(@SpringQueryMap ProductInfo productInfo);@RequestMapping("/getJson")public String getJson(@RequestBody ProductInfo productInfo);
}
⽬录结构如下:
打Jar包
通过 Maven 打包
服务提供⽅
服务提供⽅实现接⼝ ProductInterface
@RequestMapping("/product")
@RestController
public class ProductController implements ProductInterface {@Autowiredprivate ProductService productService;@RequestMapping("/{productId}")public ProductInfo getProductById(@PathVariable("productId") Integer productId){System.out.println("收到请求,Id:"+productId);return productService.selectProductById(productId);}@RequestMapping("/p1")public String p1(Integer id){return "p1接收到参数:"+id;}@RequestMapping("/p2")public String p2(Integer id,String name){return "p2接收到参数,id:"+id +",name:"+name;}@RequestMapping("/p3")public String p3(ProductInfo productInfo){return "接收到对象, productInfo:"+productInfo;}@RequestMapping("/p4")public String p4(@RequestBody ProductInfo productInfo){return "接收到对象, productInfo:"+productInfo;}
}
服务消费⽅
import com.bite.api.ProductInterface;
import org.springframework.cloud.openfeign.FeignClient;
@FeignClient(value = "product-service", path = "/product") public interface ProductApi extends ProductInterface {}
Feign 抽取⽅式
官⽅推荐 Feign 的使⽤⽅式为继承的⽅式,但是企业开发中,更多是把 Feign 接⼝抽取为⼀个独⽴的模块 (做法和继承相似,但理念不同).操作⽅法:将 Feign 的 Client 抽取为⼀个独⽴的模块,并把涉及到的实体类等都放在这个模块中,打成⼀个Jar.服务 消费⽅只需要依赖该 Jar 包即可.这种⽅式在企业中⽐较常⻅,Jar 包通常由服务提供⽅来实现.
创建⼀个 module
引⼊依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
编写 API
将全部的 open feign 客户端的代码写到到 product-api 模块
打 Jar 包
通过 Maven 打包
服务消费⽅使⽤ product-api
引⼊依赖(将刚刚打的 jar 包导入)
<dependency><groupId>org.example</groupId><artifactId>product-api</artifactId><version>1.0-SNAPSHOT</version>
</dependency>
指定扫描类: ProductApi
指定要使用哪个 open feign 客户端
在启动类添加扫描路径(路径就是 product-api 包中 open feign 客户端 ProductApi 的位置)
@EnableFeignClients(basePackages = {"com.bite.api"})
也可以指定需要加载的Feign客户端
@EnableFeignClients(clients = {ProductApi.class})
完整代码如下:
@EnableFeignClients(basePackages = {"com.bite.api"})
@SpringBootApplication
public class OrderServiceApplication {public static void main(String[] args) {SpringApplication.run(OrderServiceApplication.class, args);}
}
从本地读取Jar包
修改 pom ⽂件
<dependency>
<groupId>org.example</groupId>
<artifactId>product-api</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- scope 为system. 此时必须提供systemPath即本地依赖路径. 表⽰maven不会去中央仓库查找依赖 不推荐使⽤-->
<scope>system</scope>
<systemPath>D:/Maven/.m2/repository/org/example/product-api/1.0-SNAPSHOT/product-api-1.0-SNAPSHOT.jar</systemPath>
</dependency>/....
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><includeSystemScope>true</includeSystemScope></configuration></plugin></plugins>
</build>
把 D:/Maven/.m2/repository 改为本地仓库的路径