OpenFeign 最佳实践
问题引入
最佳实践是在项目实践中总结出的使用方式。
通过观察,我们也能看出来,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 {
@Autowired
private ProductService productService;
@RequestMapping("/{productId}")
public ProductInfo getProductById(@PathVariable("productId") Integer productId) {
return productService.selectProductById(productId);
}
}
那么有没有一种方法可以简化这种写法呢?
Feign 继承方式
Feign 支持继承的方式,我们可以把一些常见的操作封装到接口里。我们可以定义好一个接口,服务提供方实现这个接口,服务消费方编写 Feign 接口的时候,直接继承这个接口。
创建 Module
接口可以放在一个公共的 Jar 包里,供服务提供方和服务消费方使用。
引入依赖
<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>
编写接口
把之前 ProductApi 的内容移动到 Module 中的 ProductInterface 接口中:
package api;
import model.ProductInfo;
import org.springframework.cloud.openfeign.SpringQueryMap;
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.RequestParam;
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);
}
把之前 ProductInfo 的内容移动到 Module 中:
package model;
import lombok.Data;
import java.util.Date;
@Data
public class ProductInfo {
private Integer id;
private String productName;
private Integer productPrice;
private Integer state;
private Date createTime;
private Date updateTime;
}
目录结构如下:
打 Jar 包
通过 Maven 打包。
观察 Maven 本地仓库,Jar 包是否打成功。
服务提供方
服务提供方实现接口 ProductInterface。
package product.controller;
import api.ProductInterface;
import model.ProductInfo;
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;
import product.service.ProductService;
@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 "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.toString();
}
@RequestMapping("/p4")
public String p4(@RequestBody ProductInfo productInfo) {
return "product-service 接收到参数:" + productInfo.toString();
}
}
服务消费方
服务消费方继承 ProductInterface。
package order.api;
import 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>
编写接口
把之前 ProductApi 的内容移动到 Module 中的 ProductInterface 接口中:
package api;
import model.ProductInfo;
import org.springframework.cloud.openfeign.SpringQueryMap;
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.RequestParam;
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);
}
把之前 ProductInfo 的内容移动到 Module 中:
package model;
import lombok.Data;
import java.util.Date;
@Data
public class ProductInfo {
private Integer id;
private String productName;
private Integer productPrice;
private Integer state;
private Date createTime;
private Date updateTime;
}
目录结构如下:
打 Jar 包
通过 Maven 打包。
观察 Maven 本地仓库,Jar 包是否打成功。
服务消费方
删除 ProductInfo 和 ProductApi,引入依赖。
<dependency>
<groupId>com.wmh</groupId>
<artifactId>product-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
指定扫描类:下面我们使用 @EnableFeignClients(clients = {ProductApi.class}) 来指定扫描类,当然也可以使用 @EnableFeignClients(basePackages = {"api"}) 指定扫描类。
package order;
import 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);
}
}
如果不指定扫描类的话,运行程序会失败并报错。原因是因为 order-service 的启动类 OrderServiceApplication 只会扫描启动类所在目录,而 ProductApi 并不在其扫描路径内,因此需要指定扫描类。
启动服务并访问
服务部署
- 修改数据库、Nacos 等相关配置。
- 对两个服务进行打包。 Maven 打包默认是从远程仓库下载的,product-api 这个包在本地,有以下解决方案:
- 上传到 Maven 中央仓库(比较麻烦)[不推荐]
- 搭建 Maven 私服,上传 Jar 包到私服 [企业推荐]
- 从本地读取 Jar 包 [个人学习阶段推荐]
前两种方法比较复杂,咱们使用第三种方式。
修改 pom.xml 文件
如果不配置以下内容,项目启动会失败并报错:
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
- 上传 jar 到 Linux 服务器。
- 启动 Nacos。启动前最好把 data 数据删除掉。
- 启动服务。
# 后台启动 order-service,并设置输出日志到 logs/order.log
nohup java -jar order-service.jar >logs/order.log &
# 后台启动 product-service,并设置输出日志到 logs/product-9090.log
nohup java -jar product-service.jar >logs/product-9090.log &
# 启动实例,指定端口号为 9091
nohup java -jar product-service.jar --server.port=9091 >logs/product-9091.log &


