目录
OpenFeign 是 Spring Cloud 提供的一种 声明式 HTTP 客户端。它的核心作用是,让我们 像调用本地方法一样调用远程服务的 HTTP 接口,不用自己写 RestTemplate
的请求代码。
回顾之前.......中出现的订单服务远程调用产品服务中,是通过RedisTemplate实现的,参考代码如下:
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
String url = "http://product-service/product/"+ orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
当前代码功能上没有问题,但是存在一些细节上的问题:
1.封装比较臃肿,URL复杂时可能容易出错。
2.代码可读性比较差,风格不统一。
一、OpenFeign快速使用
1.引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.添加注解
在order-service的启动类添加注解 @EnableFeignClients ,开启OpenFeign的功能
@EnableFeignClients
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.*run*(OrderServiceApplication.class, args);
}
}
3.编写OpenFeign的客⼾端
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {
@RequestMapping("/{productId}")
ProductInfo getProductById(@PathVariable("productId") Integer productId);
}
@FeignClient参数说明:
name:指定FeignClient的名称,也就是微服务的名称,用于服务发现,Feign底层会使用Spring Cloud LoadBalance进行负载均衡,也可以使用url 属性指定⼀个具体的url。
path:定义当前FeignClient的统⼀前缀。
4.修改远程调用
@Autowired
private ProductApi productApi;
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
ProductInfo productInfo = productApi.getProductById(orderInfo.getProductId());
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
5.测试
启动服务,访问接口,测试远程调用:http://127.0.0.1:8080/order/1
此时就实现了更优雅的远程调用!
二、OpenFeign参数传递
1.单个参数
服务提供方
@RestController
@RequestMapping("/product")
public class ProductController {
@RequestMapping("/p1")
public String p1(Integer id){
return "p1接收到参数:"+id;
}
}
OpenFeign客户端
@FeignClient(value = "product-service", path ="/product")
public interface ProductApi {
@RequestMapping("/p1")
String p1(@RequestParam("id") Integer id);
}
注意: @RequestParam 做参数绑定, 不能省略
服务消费方
@RequestMapping("/feign")
@RestController
public class TestFeignController {
@Autowired
private ProductApi productApi;
@RequestMapping("/o1")
public String o1(Integer id) {
return productApi.p1(id);
}
}
2.多个参数
服务提供方
@RequestMapping("/p2")
public String p2(Integer id,String name){
return "p2接收到参数,id:"+id +",name:"+name;
}
OpenFeign客户端
@RequestMapping("/p2")
String p2(@RequestParam("id")Integerid,@RequestParam("name")String name);
服务消费方
@RequestMapping("/o2")
public String o2(@RequestParam("id")Integer id,@RequestParam("name")String name{
return productApi.p2(id,name);
}
4.传递对象
服务提供方
@RequestMapping("/p3")
public String p3(ProductInfo productInfo){
return "接收到对象, productInfo:"+productInfo;
}
OpenFeign客户端
@RequestMapping("/p3")
String p3(@SpringQueryMap ProductInfo productInfo);
服务消费方
@RequestMapping("/o3")
public String o3(ProductInfo productInfo){
return productApi.p3(productInfo);
}
5.传递JSON
服务提供方
@RequestMapping("/p4")
public String p4(@RequestBody ProductInfo productInfo) {
return "接收到对象, productInfo:"+productInfo;
}
OpenFeign客户端
@RequestMapping("/p4")
String p4(@RequestBody ProductInfo productInfo);
服务消费方
@RequestMapping("/o4")
public String o4(@RequestBody ProductInfo productInfo) {
return productApi.p4(productInfo);
}
三、OpenFeign的最佳实践
1.继承的方式
我们可以定义好⼀个接口,服务提供方实现这个接口,服务消费方编写Feign接口的时候, 直接继承这个接口。
定义接口
public interface ProductInterface {
@RequestMapping("/{productId}")
ProductInfo getProductById(@PathVariable("productId") Integer productId);
@RequestMapping("/p1")
String p1(@RequestParam("id") Integer id);
@RequestMapping("/p2")
String p2(@RequestParam("id")Integer id,@RequestParam("name")String name);
@RequestMapping("/p3")
String p3(@SpringQueryMap ProductInfo productInfo);
@RequestMapping("/p4")
String p4(@RequestBody ProductInfo productInfo);
}
服务提供方实现接口
@RequestMapping("/product")
@RestController
public class ProductController implements ProductInterface {
@Autowired
private ProductService productService;
@RequestMapping("/{productId}")
public ProductInfo getProductById(@PathVariable("productId") Integer 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;
}
}
服务消费方继承接口
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi extends ProductInterface {
}
2.抽取的方式
将Feign的Client抽取为⼀个独立的模块,并把涉及到的实体类等都放在这个模块中,打成⼀个jar包,服务消费方只需要依赖该jar包即可,这种方式在企业中比较常见,jar包通常由服务提供方来实现。
①创建module,命名为product-api
②引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
③编写api
复制 ProductApi, ProductInfo 到product-api模块中。
④打jar包
通过Maven打包
⑤服务消费方引入jar包
⑥在启动类添加扫描路径
@EnableFeignClients(clients = {ProductApi.class})
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.*run*(OrderServiceApplication.class, args);
}
}
两种方式对比
在实际项目中,抽取方式比继承方式更为常用,因为它更加灵活,并且符合面向接口编程的原则,同时也避免了继承可能带来的紧耦合问题。