上篇博客简单介绍了feignclient基本知识和使用方法,这篇博客用来介绍feignclient进阶使用。
上篇博客介绍的是单个工程简单使用FeignClient的例子,如果在多工程情况下,每个工程都需要增删改查多张表呢?比如现在有多个业务微服务工程proj1
,proj2
,proj3
……都需要增删改查db库的多张表user
、order
、good
……(假设此处proj,proj2,proj3
……都有其他业务职责,不直接连接数据库)。
最简单的做法就是每个工程都声明一个自己的feign客户端,然后在客户端里面定义好对db
库多张表的增删改查请求方法,另外有一个连接db
库的工程projdb
专门有一个controller来响应这些方法。此时可能会有一个问题,为什么proj1,proj2……
不直接依赖projdb
,然后将其一起打包进jar包运行呢?这是因为使用feign客户端会比直接依赖projdb
更轻量级,projdb
要访问数据库,必然包括mybatis-xml
文件或其他访问数据库代码,这种访问数据库的细节对于proj1、proj2
来说是需要屏蔽的,因此通常不会直接让微服务工程依赖数据库工程。
然而,上述方法会存在一些问题:
projdb
工程使用一个controller响应,存在与2相同的问题;try-catch
处理或者在FeignClient注解里定义fallback
属性。针对这些问题,可以考虑如下改进:
db-api
,其他微服务proj1、proj2、proj3……
依赖db-api
;db-api
里面定义多个feign客户端,每个客户端对应一张表的增删改查请求;projdb
同样使用多个controller响应请求,每个controller对应一张表。为了使得请求不被遗漏,projdb
里的controller可以实现feign客户端接口,这样可以保持方法一一对应,且可以省略@RequestMapping
及其子注解,因为请求的映射路径会被继承下来;ResultResponse
作为返回值,它包括responseCode
、responseMsg
、data
。异常捕获放在controller中,出现异常则返回code为errorCode
的ResultResponse
,通过code值来判断响应是否正常;进行到这一步,似乎上述问题都已解决,只需要在要请求的地方调用feignclient接口方法即可,但是若要访问的表数量随着项目发展不断扩大,再在代码里注入客户端Bean,使用各种接口方法去发送请求并从返回体中取数据会较难维护,代码没有那么清晰明了,因此可以提供一个统一的入口,通过这个入口来实现对任一客户端的调用,这个入口即静态工厂,通过静态工厂实现按需直接发送请求(无需注入Bean),静态工厂处理ResultResponse
并判断responseCode
以及取出data
返回,这样能够对业务工程屏蔽feign客户端细节,让业务代码能够直接取到所需数据。因此,最后一步改进为:
db-api
工程提供一个静态工厂ApiFactory
,工厂里包含所有定义的feignclient,通过静态方法来实现对某一客户端某一方法的调用并获得返回结果,比如业务工程通过ApiFactory.getUserById("1")
这一行代码就能获得一个id
为1的User
对象。按照上述思路梳理一下现有工程及相互之间的依赖关系:
db-api
,定义多个feign接口,提供静态工厂访问入口、通用响应类ResultResponse
;proj1、proj2、proj3
……使用db-api
静态工厂访问数据库数据,依赖db-api
;projdb
,controller实现feign接口,依赖db-api;
//业务工程
proj1
├── (依赖 db-api)
proj2
├── (依赖 db-api)
proj3
└── (依赖 db-api)
//数据库响应工程
projdb
└── (依赖 db-api)
//feign客户端工程
db-api
三个模块之间的类图关系如下所示,另有两个大箭头标注工程依赖关系和请求响应关系。
业务工程proj1、2、3
通过db-api
工程的ApiFactory
类的静态方法实现对数据库的访问和修改。
静态工厂ApiFactory
包含所有feign客户端Api作为静态成员变量(定义成静态是因为静态方法只能访问静态成员变量),同时定义包含了调用Feignclient
接口方法的各种静态方法,每个方法都会对feign客户端返回值ResultResponse
进行处理,若responseCode
正常则取出其包含的data
并返回,否则返回空或抛出异常。
projdb
是真实访问数据库的工程,db-api
工程的Feign客户端Api请求类和projdb
的controller响应类一一对应,为实现关系。Api发送的请求将由controller同名方法进行响应。controller将对访问数据库的结果进行封装,返回ResultResponse
。
假设上述工程在注册中心的注册名为:
proj1/proj2/proj3: biz-module
db-api: db-api-module
projdb: db-module
下面给出具体代码实施。
ResultResponse
类的responseCode
和responseMsg
可以用枚举类表示,先定义枚举ResponseCode
:
public enum ResponseCode {
SUCCESS("200", "成功"),
FAILURE("500", "失败");
private final String code;
private final String message;
ResponseCode(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
ResultResponse
类:
public class ResultResponse<T> {
private String responseCode;
private String responseMsg;
private T data;
public ResultResponse() {
}
public ResultResponse(String responseCode, String responseMsg, T data) {
this.responseCode = responseCode;
this.responseMsg = responseMsg;
this.data = data;
}
public static <T> ResultResponse<T> success(T data) {
return new ResultResponse<>(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getMessage(), data);
}
public static <T> ResultResponse<T> fail(String message) {
return new ResultResponse<>(ResponseCode.FAILURE.getCode(), message, null);
}
// 自定义其他返回方法……
}
Feignclient接口类/客户端Api请求类IUserApi
、IGoodsApi
、IOrderApi
。
@FeignClient(contextId = "IUserApi", name = "db-module")
public interface IUserApi {
@GetMapping("/users/{id}")
ResultResponse<User> getUserById(@PathVariable("id") int id);
@PostMapping("/users")
ResultResponse<Integer> insert(@RequestBody User user);
}
@FeignClient(contextId = "IGoodsApi", name = "db-module")
public interface IGoodsApi {
@GetMapping("/goods/{id}")
ResultResponse<Goods> getGoodsById(@PathVariable("id") int id);
@DeleteMapping("/goods/{id}")
ResultResponse<Integer> delete(@PathVariable("id") int id);
}
@FeignClient(contextId = "IOrderApi", name = "db-module")
public interface IOrderApi {
@GetMapping("/orders/{id}")
ResultResponse<Order> getOrderById(@PathVariable("id") int id);
@PutMapping("/orders")
ResultResponse<Integer> update(@RequestBody Order order);
}
ApiFactory
作为使用feignclient的入口,提供所有调用feign服务的静态方法。
@Component
public class ApiFactory {
// 静态成员变量
private static IUserApi userApi;
private static IGoodsApi goodsApi;
private static IOrderApi orderApi;
@Autowired
public ApiFactory(IUserApi userApi, IGoodsApi goodsApi, IOrderApi orderApi) {
ApiFactory.userApi = userApi;
ApiFactory.goodsApi = goodsApi;
ApiFactory.orderApi = orderApi;
}
// 用户相关 API 调用
public static User getUserById(int id) {
try {
ResultResponse<User> response = userApi.getUserById(id);
return handleResponse(response);
} catch (Exception e) {
// 处理异常(可以记录日志或抛出自定义异常)
throw new RuntimeException("获取用户失败: " + e.getMessage(), e);
}
}
public static Integer insertUser(User user) {
try {
ResultResponse<Integer> response = userApi.insert(user);
return handleResponse(response);
} catch (Exception e) {
throw new RuntimeException("插入用户失败: " + e.getMessage(), e);
}
}
// 商品相关 API 调用
public static Goods getGoodsById(int id) {
try {
ResultResponse<Goods> response = goodsApi.getGoodsById(id);
return handleResponse(response);
} catch (Exception e) {
throw new RuntimeException("获取商品失败: " + e.getMessage(), e);
}
}
public static Integer deleteGoods(int id) {
try {
ResultResponse<Integer> response = goodsApi.delete(id);
return handleResponse(response);
} catch (Exception e) {
throw new RuntimeException("删除商品失败: " + e.getMessage(), e);
}
}
// 订单相关 API 调用
public static Order getOrderById(int id) {
try {
ResultResponse<Order> response = orderApi.getOrderById(id);
return handleResponse(response);
} catch (Exception e) {
throw new RuntimeException("获取订单失败: " + e.getMessage(), e);
}
}
public static Integer updateOrder(Order order) {
try {
ResultResponse<Integer> response = orderApi.update(order);
return handleResponse(response);
} catch (Exception e) {
throw new RuntimeException("更新订单失败: " + e.getMessage(), e);
}
}
// 处理 API 响应
private static <T> T handleResponse(ResultResponse<T> response) {
if (ResponseCode.SUCCESS.getCode().equals(response.getResponseCode())) {
return response.getData(); // 返回正常的数据
} else {
throw new RuntimeException(response.getResponseMsg()); // 抛出异常,可自定义异常
}
}
}
上面的代码是使用构造函数注入相关成员变量,也可以使用@PostConstruct
,这样的话需要先声明普通成员变量并自动注入:
@Component
public class ApiFactory {
private static IUserApi userApi;
private static IGoodsApi goodsApi;
private static IOrderApi orderApi;
@Autowired
private IUserApi userApiInstance;
@Autowired
private IGoodsApi goodsApiInstance;
@Autowired
private IOrderApi orderApiInstance;
@PostConstruct
public void init() {
userApi = userApiInstance;
goodsApi = goodsApiInstance;
orderApi = orderApiInstance;
}
该工程响应feignclient的controller均实现于feignclient接口,这样可以省略映射地址,保证方法一一对应。代码流程类似,统一使用ResultResponse
作为返回值。
@RestController
public class UserApi implements IUserApi {
@Autowired
private UserMapper userMapper;
@Override
public ResultResponse<User> getUserById(@PathVariable int id) {
try {
User user = userMapper.selectById(id);
return new ResultResponse<>(ResponseCode.SUCCESS.getCode(), "获取用户成功", user);
} catch (Exception e) {
return new ResultResponse<>(ResponseCode.FAIL.getCode(), "获取用户失败: " + e.getMessage(), null);
}
}
@Override
public ResultResponse<Integer> insert(@RequestBody User user) {
try {
int result = userMapper.insert(user);
return new ResultResponse<>(ResponseCode.SUCCESS.getCode(), "插入用户成功", result);
} catch (Exception e) {
return new ResultResponse<>(ResponseCode.FAIL.getCode(), "插入用户失败: " + e.getMessage(), null);
}
}
}
@RestController
public class GoodsApi implements IGoodsApi {
@Autowired
private GoodsMapper goodsMapper;
@Override
public ResultResponse<Good> getGoodById(@PathVariable int id) {
try {
Goods goods = goodsMapper.selectById(id);
return new ResultResponse<>(ResponseCode.SUCCESS.getCode(), "获取商品成功", good);
} catch (Exception e) {
return new ResultResponse<>(ResponseCode.FAIL.getCode(), "获取商品失败: " + e.getMessage(), null);
}
}
@Override
public ResultResponse<Integer> delete(@PathVariable int id) {
try {
int result = goodsMapper.delete(id);
return new ResultResponse<>(ResponseCode.SUCCESS.getCode(), "删除商品成功", result);
} catch (Exception e) {
return new ResultResponse<>(ResponseCode.FAIL.getCode(), "删除商品失败: " + e.getMessage(), null);
}
}
}
@RestController
public class OrderApi implements IOrderApi {
@Autowired
private OrderMapper orderMapper;
@Override
public ResultResponse<Order> getOrderById(@PathVariable int id) {
try {
Order order = orderMapper.selectById(id);
return new ResultResponse<>(ResponseCode.SUCCESS.getCode(), "获取订单成功", order);
} catch (Exception e) {
return new ResultResponse<>(ResponseCode.FAIL.getCode(), "获取订单失败: " + e.getMessage(), null);
}
}
@Override
public ResultResponse<Integer> update(@RequestBody Order order) {
try {
int result = orderMapper.update(order);
return new ResultResponse<>(ResponseCode.SUCCESS.getCode(), "更新订单成功", result);
} catch (Exception e) {
return new ResultResponse<>(ResponseCode.FAIL.getCode(), "更新订单失败: " + e.getMessage(), null);
}
}
}
所有准备工作都已完成,现在只需要使用即可,以proj3
为例,如果在某个方法中要使用feignclient,则只需要在相应类中写ApiFactory.xxxx(xxx)
即可,比如:
@Service
class UserService{
public void userBiz(int id){
// ……
User user = ApiFactory.getUserById(id);
// ……
}
}
class GoodsService{
public void goodsBiz(int id){
// ……
int res = ApiFactory.deleteGoodsById(id);
// ……
}
}
综上就是本篇博客的所有内容,这种架构使得调用feignclient的流程对于业务代码来说是相对无感知的,业务代码执行ApiFactory
静态方法并获取结果的流程就好像自己直接访问了数据库一样。