随着微服务架构的普及,服务间远程调用成为开发常态。相比于繁琐的 RestTemplate 手动调用方式,OpenFeign 以声明式接口调用、简洁易用的特性,成为 Spring Cloud 生态中跨服务通信的主流方案。
最佳实践指的是根据历史的迭代,在项目实践过程中总结出来最好的方式。其中,我们总结出来两种方案,继承方式和抽方式
开始了解之前,前提提要,这次我们将以一个简单的模拟电商父子工程项目来进行演示最佳实现,其中此项目包含三个模块:order-service(客户端)、product-service(服务器) 和 product-api,当服务启动时,order-service 和 product-service 对应的服务会被注册到 nacos 上,然后 order-service 通过OpenFeign 对 product-service 模块发起服务调用,此外,还添加多了一个 product-api 模块,用于后续的演示,下图是关系图:

1.1Feign继承方式
这个所谓的继承就是想通过减少多余的代码,让外部的接口继承给想继承的模块,鉴于此,若我们想创建一个接口的话,既不能将具体方法同步给客户端(以免代码冗余),又不能将web服务给其他第三方程序引入,所以这次就是将product-api这个api继承到product-service这个服务里面。
1.1.1操作方法
首先当我们做好前期的准备工作后,将用到的实体类productInfo和productInterface创建好后

我们就把这个打包成一个jar包,放到maven仓库,让product-service和order-service这两个模块后续通过pom文件引入依赖来获得这个api

XML
<dependency>
<groupId>com.bit.demo</groupId>
<artifactId>product-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
当引入完之后,就是一系列的进行继承,让原来的模块继承这个新的api
Product-service
java
package com.bite.product.controller;
import com.bite.product.api.ProductInterface;
import com.bite.product.model.ProductInfo;
import com.bite.product.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/product")
public class ProductController implements ProductInterface {
@Autowired
private ProductService productService;
@RequestMapping("/{productId}")
public ProductInfo getProductInfo(@PathVariable("productId") Integer productId) {
log.info("接收到参数:productId",productId);
return productService.selectProductById(productId);
}
@RequestMapping("/p1")
public String p1(Integer id){
return "product-service 接收到一个参数,id:" + id;
}
@RequestMapping("/p2")
public String p2(Integer id, String name){
return "product-service 接收到一个参数,id:" + id + "name: " + name;
}
@RequestMapping("/p3")
public String p3(ProductInfo productInfo){
return "product-service 接收到一个参数,productInfo:" + productInfo.toString();
}
@RequestMapping("/p4")
public String p4(@RequestBody ProductInfo productInfo){
return "product-service 接收到一个参数,productInfo:" + productInfo.toString();
}
}
Order-service
java
package com.bite.order.api;
import org.springframework.cloud.openfeign.FeignClient;
@FeignClient(value = "product-service",path = "product")
public interface ProductInterface extends com.bite.product.api.ProductInterface {
}
当我们搞完后这个些,进行测试,发现是成功的

1.2Feign抽取方式
这个相较于上面的继承方式,它的重点更多是偏向将接口抽取成为一个独立的模块,与继承类似,但不同的是,他这个可以减去Order-service的Feign接口声明
因此,我们此次的product-api模块里面放置的是提取出来的公共代码,像这次的order-service这里面的product-api、product-service的productInfo,我们就把他提取到这个模块里面

接着,就是和上面一样的打包到仓库,导入依赖到需要调用的模块的操作,但这里可能会出现一个问题,就是idea会报错说ProductApi这个类没有被扫到,那是因为spring启动的时候,默认只扫描当前的启动类所在的目录,但由于这个ProductApi不在这里,所以就报错。
1.2.1解决外部类无法被spring扫描的问题
为了解决这个问题,官方提供了@EnableFeignClients这个注解,其中有包含了两种解决方案。
其一就是通过basePakage这个,指定扫描的路径
其二就是使用client这个方式引入第三方的一个Feign声明
java
package com.bite.order;
import com.bite.product.api.ProductApi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients(clients = {ProductApi.class})
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class,args);
}
}
经过调整后测试,也是成功的

1.3总结
因此,综上所述,当我们想要方便,简单,代码少的话,就使用继承的方法,但是缺点是不能耦合性高,跨微服务不能使用。当我们抽取公共SDK或者是远程调用的话,我们就能使用抽取的方案
https://gitee.com/mount-everest-g20/best_-practices_for_-microservices,这里是两个学习项目的码云地址
,感兴趣的可以下载到本地看下,本期就到这啦,欢迎下次再见