《OpenFeign 最佳实践:三大优雅调用远程服务的方式》
1.RestTemplate存在的问题
观察一下我们远程调用的代码
虽说RestTemplate对HTTP封装后,已经比直接使用HTTPClient简单放便很多了,但是还是存在一些问题:
1.需要拼接url,虽然灵活性高,但是封装臃肿,url复杂时,容易出错。
2.代码可读性差,风格不统一
这些问题,我们都可以通过OpenFeign来解决
2.OpenFeign介绍
OpenFeign是一个声明式的Web Service客户端,它让微服务间的调用更为简单,类似与controller调用service接口,只需要创建一个接口,然后让服务调用方添加注解即可使用OpenFeign
OpenFeign的前身
可以简单理解为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
3.OpenFeign的快速上手
注意:这里的快速上手是基于前面的nacos服务的代码
1.引入依赖
在服务调用方的pom文件中引入下面的依赖,由于我的是订单服务调用产品服务,所以订单服务是服务调用方,所以在订单服务中引入下面的依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
2.添加注解
在订单服务中的启动类中添加@EnableFeignClients,开启OpenFeign的功能,如下图
3.编写OpenFeign的客户端
基于SpringMVC的注解来声明远程调用的信息,这个注解就是@FeignClient,我们单独将OpenFeign的客户端作为一个接口抽出来,如下图
代码如下
@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {@RequestMapping("/{productId}")ProductInfo getProductById(@PathVariable("productId") Integer productId);
}
@FeignClient注解作用在接口上,参数说明:
1.name/value:指定FeignClient客户端从Nacos注册中心调用的微服务名称(),该属性用来服务发现
2.path:为FeignCient客户端的所有接口方法添加一个公共的前缀路径,避免在每一个方法上重复编写相同的路径
4.修改远程调用代码
修改远程调用的方法
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate ProductApi productApi;public OrderInfo selectByOrderId(Integer id){OrderInfo orderInfo = orderMapper.selectByOrderId(id);ProductInfo productInfo=productApi.selectByProductId(orderInfo.getProductId());orderInfo.setProductInfo(productInfo);return orderInfo;}}
前后代码对比
5.测试接口
启动服务,远程调用接口,发现服务调用成功
可以看出来,使用Feign也可是实现远程调用,并且Feign简化了与HTTP服务交互的的过程,把REST客户端的定义转换为Java接口,并通过注解的方式来声明请求参数,请求方式等信息,使远程调用更加方便和间接
4.OpenFeign参数传递
1.传递单个参数
服务提供方product-service
@Slf4j
@RequestMapping("/product")
@RestController
public class ProductController implements ProductInterface {@Autowiredprivate ProductService productService;@RequestMapping("/p1")public String p1(Integer id){return "product-service接收参数:"+id;}
}
Feign客户端:
@FeignClient(value = "product-service",path = "/product")
public interface ProductApi extends ProductInterface {@RequestMapping("/p1")String p1(@RequestParam("id") Integer id);
}
注意:@RequestParam做参数绑定,不能省略
服务消费方orders-service:
@RequestMapping("/feign")
@RestController
public class TestFeignController {@Autowiredprivate ProductApi productApi;@RequestMapping("/o1")public String o1(Integer id){return productApi.p1(id);}
}
测试接口: 发现成功调用服务
2.传递多个参数
服务提供方product-service
Feign客户端:
注意:@RequestParam注解不能省略
服务消费方:orders-service服务的FeignController
测试接口:服务成功调用
3.传递对象
服务提供方product-service:
Feign客户端:ProductApi:
Feign在接收对象类型的参数时,要在前面加一个@SpringQueryMap注解
服务消费方: orders-service服务的FeignController
测试接口: 成功调用远程服务
4.传递JSON数据
服务提供方:product-service
Feign客户端:ProductApi:
服务消费方:orders-service服务的FeignController
测试接口:远程调用服务成功
调用关系图,如下
5.Feign的最佳实践
我们发现Feign中的方法声明和ProductController中的代码及其相似,其实是有两种方式去优化这写重复的部分的
5.1Feign继承式
Feign支持继承的方式,我们可以将一些常见的操作封装到接口中,让服务提供方product-service去实现接口中声明的方法,让服务消费方的Feign直接继承这个接口即可
1.首先创建一个子工程---product-api
在ProductInterface的pom文件中引入下面的依赖,如下图
<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>
2.编写接口
创建一个ProductInterface接口
如下图
由于我们将ProductInfo也复制到ProductInteface工程中了,此时将orders-service和product-service的ProductInfo类给注释掉了,因为ProductInfo是重复的代码,我们将其放在ProductInteface工程中就行了,如果想要在orders-service和product-service使用ProductInteface工程中的ProductInfo,由于是不同的过程,所以我们要将ProductInteface工程打包,将ProductInteface工程的jar包存储到我们本地的Maven仓库中,后续想使用ProductInteface工程中的ProductInfo,只需注入依赖即可
出现下面这部分就代表打包成功了
3.服务提供方代码编写
此时product-service的pom文件就会多出下面一部分依赖,这部分依赖就是ProductInteface工程的jar包的依赖,如下图
4.Feign客户端编写
此时发现Feign客户端中就省略了方法的声明
5.服务消费方编写
此时需要注意的是由于此时使用的是ProductInterface里面的ProductInfo类,此时要重新导入ProductInfo的包,不然会报错
没啥大变化,不过就是使用ProductInfo要重新导包
5.测试接口
服务调用成功
5.2Feign抽取方式
抽取的做法其实和继承的操作很相似,但是理念不同,抽取是直接将Feign的客户端也作为一个独立的模块,打成一个Jar包,后面服务的消费方只需要依赖该Jar即可。
简单来说,抽取就是直接创建一个子工程,在该子工程中直接完成Feign客户端的编写,后面服务消费方向使用工程中的Feign的客户端时,引入工程的jar包依赖就行了
1.创建一个工程---product-api
先在product-api工程中引入下面的依赖:
<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>
然后直接在该工程中完成Feign客户端的编写,如下图
然后将该工程打包,保存在本地的Maven仓库中
2.Product-service服务的修改
1.引入product-api工程jar包的依赖
2.删除ProductApi和ProductInfo,统一使用product-api工程中的ProductApi和ProductInfo,此时还要将ProductApi和ProductInfo的路径修改为product-api的路径
3.orders-service服务的修改
1.还是需要引入product-api工程jar包的依赖
2.只需在启动类中指定需要加载的Feign客户端即可,如下图
4.测试接口
发现服务调用成功