Appearance
FeignNode 动态服务调用框架使用文档
一、概述
FeignNode 是一个基于 Spring Cloud Feign 的动态服务调用框架,专为 LangGraph 智能体工作流设计。它允许在运行时动态调用任意已注册的 Feign 服务接口,无需硬编码调用逻辑,极大提升了微服务间调用的灵活性。
核心特性
- 自动注册: 启动时自动扫描并注册所有 Feign 客户端接口
- 动态调用: 支持通过类名和方法名动态调用远程服务
- 类型安全: 完善的参数类型转换和校验机制
- 智能体集成: 无缝集成到 LangGraph 工作流节点中
- 统一错误处理: 标准化的结果返回和异常处理
- 性能监控: 内置调用耗时统计和详细日志记录
二、架构设计
2.1 核心组件
FeignNode框架
├── FeignRegistryInitializer # Feign接口注册器
├── FeignClassInfoDTO # Feign类元信息
├── FeignMethodInfoDTO # Feign方法元信息
├── MethodInvoker # 方法调用器
├── FeignNodeAction # LangGraph节点动作
└── MemberFieldConstructorDTO # 参数类型转换器2.2 工作流程
1. 应用启动
└─> FeignRegistryInitializer 扫描所有 @FeignClient 注解的接口
└─> 提取接口的类信息、方法信息、参数信息
└─> 存储到注册表 registry 中
2. 运行时调用
└─> 从 registry 获取 Feign 接口信息
└─> 构建 MethodInvoker 调用器
└─> 转换和校验参数
└─> 执行反射调用
└─> 返回标准化结果三、快速开始
3.1 定义 Feign 客户端
java
@FeignClient(value = ServiceNameConstants.AUDIT_SERVICE)
public interface AuditTaskExecutionMaterialFeignService {
/**
* 根据任务执行ID获取关联的文档库列表
* @param taskExecutionId 任务执行ID
* @return 文档库列表
*/
@Operation(
summary = "获取任务执行资料关联的文档库",
description = "根据任务执行ID查询所有关联的文档库信息"
)
@GetMapping("/audit-task-execution-material/document-libraries/{taskExecutionId}")
R<List<Long>> getDocumentLibrariesByTaskExecutionId(
@PathVariable("taskExecutionId") Long taskExecutionId
);
}说明:
- 使用
@Operation注解添加方法描述,便于后续查询和使用 - 方法签名必须清晰,参数类型明确
3.2 在 LangGraph 工作流中使用
java
@Component
public class AuditReportProcessAgent {
@Resource
ReportMiddleContentNodeAction reportMiddleContentNodeAction;
public Boolean start() throws GraphStateException {
// 1. 准备调用参数
String feignClass = "com.pig4cloud.pigx.audit.feign.service.AuditTaskExecutionMaterialFeignService";
String feignMethod = "getDocumentLibrariesByTaskExecutionId";
Object[] requestParams = new Object[] {
1989250342346940417L // 任务执行ID
};
// 2. 从注册表获取Feign接口信息
FeignClassInfoDTO feignClassInfo = FeignRegistryInitializer.registry.get(feignClass);
FeignMethodInfoDTO methodInfo = feignClassInfo.getMethodInfos().get(feignMethod);
// 3. 构建方法调用器
MethodInvoker methodInvoker = MethodInvoker.builder()
.instance(feignClassInfo.getInstance())
.method(methodInfo.getMethod())
.methodName(feignMethod)
.args(requestParams)
.build();
// 4. 创建FeignNode动作
FeignNodeAction feignNodeAction = new FeignNodeAction(methodInvoker);
// 5. 定义工作流
StateGraph<ConversationState> stateGraph = new StateGraph<>(ConversationState.SCHEMA, ConversationState::new)
.addNode("getAuditReportData", node_async(feignNodeAction))
.addNode("processReportContent", node_async(reportMiddleContentNodeAction))
.addEdge(StateGraph.START, "getAuditReportData")
.addEdge("getAuditReportData", "processReportContent")
.addEdge("processReportContent", StateGraph.END);
// 6. 编译并执行
CompiledGraph<ConversationState> compiledGraph = stateGraph.compile();
Map<String, Object> initialState = Map.of(
ConversationState.MESSAGES_KEY,
"开始执行审计报告生成流程"
);
compiledGraph.invoke(initialState).ifPresent(finalState -> {
System.out.println("工作流执行完成: " + finalState);
});
return true;
}
}四、高级用法
4.1 参数类型转换
框架支持自动进行参数类型转换,适用于以下场景:
4.1.1 从字符串转换为目标类型
java
// 准备字符串参数
String[] args = {"1989250342346940417"}; // Long类型参数的字符串形式
// 获取方法参数信息
List<MemberFieldConstructorDTO> parameters = methodInfo.getParameters();
// 自动转换为目标类型
Object[] requestParams = new Object[args.length];
for (int i = 0; i < parameters.size(); i++) {
MemberFieldConstructorDTO paramConverter = parameters.get(i);
// 自动将字符串转换为Long类型
requestParams[i] = paramConverter.getConvertedValue(args[i]);
}4.1.2 支持的类型转换
- 基本类型: int, long, double, float, boolean, byte, short, char
- 包装类型: Integer, Long, Double, Float, Boolean, Byte, Short, Character
- 字符串: String
- 复杂对象: 通过 JSON 反序列化转换
- 集合类型: List, Set, Map (JSON格式)
4.1.3 特殊格式支持
java
// 数字格式(带千分位)
"1,000,000" -> 1000000 (Integer/Long)
// 布尔值多种表示
"true", "1", "yes", "on" -> true
"false", "0", "no", "off" -> false
// JSON对象
"{\"id\":123,\"name\":\"test\"}" -> Java Object4.2 复杂参数场景
4.2.1 多参数调用
java
// Feign接口定义
@PostMapping("/report/create")
R<ReportEntity> createReport(
@RequestParam Long projectId,
@RequestParam String reportName,
@RequestBody ReportConfig config
);
// 调用示例
Object[] requestParams = new Object[] {
1234567890L, // projectId
"2024年度审计报告", // reportName
new ReportConfig(...) // config 对象
};
MethodInvoker methodInvoker = MethodInvoker.builder()
.instance(feignInstance)
.method(method)
.methodName("createReport")
.args(requestParams)
.build();4.2.2 可选参数处理
java
// Feign接口定义
R<List<User>> queryUsers(
@RequestParam String name,
@Nullable @RequestParam String email // 可选参数
);
// 调用示例
Object[] requestParams = new Object[] {
"张三", // name (必填)
null // email (可选,允许为null)
};注意:
- 使用
@Nullable注解标注的参数允许传入 null - 未标注
@Nullable的参数如果传入 null 会抛出异常
4.3 错误处理
4.3.1 调用失败处理
java
FeignNodeAction feignNodeAction = new FeignNodeAction(methodInvoker);
try {
Map<String, Object> result = feignNodeAction.apply(conversationState);
// 获取调用结果
JSONObject toolResult = (JSONObject) result.get(ConversationState.TOOL_RESULT_KEY);
// 检查调用是否成功
if (toolResult.containsKey("success") && !toolResult.getBoolean("success")) {
// 处理失败情况
String errorMsg = toolResult.getString("message");
log.error("服务调用失败: {}", errorMsg);
} else {
// 处理成功情况
JSONObject data = toolResult.getJSONObject("data");
// 业务处理...
}
} catch (Exception e) {
log.error("FeignNode执行异常", e);
}4.3.2 返回结果格式
成功时:
json
{
"data": {
// 实际返回的业务数据
}
}失败时:
json
{
"success": false,
"code": 500,
"message": "错误描述",
"data": null
}异常时:
json
{
"success": false,
"error": "异常类型",
"message": "异常信息"
}五、最佳实践
5.1 定义常量类
避免硬编码类名和方法名:
java
public class FeignServiceConstants {
// Feign 服务类名
public static final String AUDIT_TASK_MATERIAL_SERVICE =
"com.pig4cloud.pigx.audit.feign.service.AuditTaskExecutionMaterialFeignService";
public static final String AUDIT_REPORT_SERVICE =
"com.pig4cloud.pigx.audit.feign.service.AuditReportFeignService";
// 方法名
public static final String METHOD_GET_DOC_LIBRARIES =
"getDocumentLibrariesByTaskExecutionId";
public static final String METHOD_CREATE_REPORT =
"createReport";
}
// 使用
String feignClass = FeignServiceConstants.AUDIT_TASK_MATERIAL_SERVICE;
String feignMethod = FeignServiceConstants.METHOD_GET_DOC_LIBRARIES;5.2 封装通用调用方法
java
@Component
public class FeignNodeHelper {
/**
* 快速创建FeignNodeAction
*/
public static FeignNodeAction createFeignNode(
String feignClass,
String methodName,
Object... args) {
// 获取Feign接口信息
FeignClassInfoDTO classInfo = FeignRegistryInitializer.registry.get(feignClass);
if (classInfo == null) {
throw new IllegalArgumentException("未找到Feign服务: " + feignClass);
}
FeignMethodInfoDTO methodInfo = classInfo.getMethodInfos().get(methodName);
if (methodInfo == null) {
throw new IllegalArgumentException("未找到方法: " + methodName);
}
// 构建调用器
MethodInvoker methodInvoker = MethodInvoker.builder()
.instance(classInfo.getInstance())
.method(methodInfo.getMethod())
.methodName(methodName)
.args(args)
.build();
return new FeignNodeAction(methodInvoker);
}
}
// 使用
FeignNodeAction action = FeignNodeHelper.createFeignNode(
FeignServiceConstants.AUDIT_TASK_MATERIAL_SERVICE,
FeignServiceConstants.METHOD_GET_DOC_LIBRARIES,
1989250342346940417L
);5.3 添加详细的接口文档
在 Feign 接口上使用 @Operation 注解添加详细说明:
java
@Operation(
summary = "获取任务执行资料",
description = "根据任务ID和执行阶段查询相关的审计资料列表," +
"包括文档、图片、视频等多种类型的资料"
)
@GetMapping("/materials/{taskId}")
R<List<MaterialVO>> getMaterials(
@PathVariable Long taskId,
@RequestParam(required = false) String stage
);5.4 性能优化建议
缓存 Feign 接口信息
java// 不要每次都从registry查找 // 不推荐 for (int i = 0; i < 1000; i++) { FeignClassInfoDTO info = FeignRegistryInitializer.registry.get(className); } // 推荐 FeignClassInfoDTO info = FeignRegistryInitializer.registry.get(className); for (int i = 0; i < 1000; i++) { // 使用缓存的info }批量调用优化
java// 如果需要调用多个服务,可以并行执行 List<CompletableFuture<R<?>>> futures = new ArrayList<>(); for (TaskConfig config : taskConfigs) { CompletableFuture<R<?>> future = CompletableFuture.supplyAsync(() -> { MethodInvoker invoker = createInvoker(config); return invoker.invokeService(); }); futures.add(future); } // 等待所有调用完成 CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
六、故障排查
6.1 常见问题
问题1: "未找到Feign客户端"
错误信息:
java.lang.NullPointerException: Cannot invoke "FeignClassInfoDTO.getMethodInfos()"
because "feignClassInfo" is null原因:
- Feign 接口未正确注册
- 类名拼写错误
解决方案:
java
// 检查注册表中是否存在该服务
Set<String> registeredServices = FeignRegistryInitializer.registry.keySet();
System.out.println("已注册的Feign服务: " + registeredServices);
// 使用正确的全限定类名
String correctClassName = "com.pig4cloud.pigx.audit.feign.service.YourFeignService";问题2: "参数类型不匹配"
错误信息:
参数 taskId 类型不匹配: 期望类型=java.lang.Long, 实际类型=java.lang.Integer解决方案:
java
// 确保传入的参数类型与方法签名一致
Object[] args = new Object[] {
1234567890L, // Long 类型,注意后缀 L
// 而不是 1234567890 // Integer 类型
};问题3: "方法不存在"
错误信息:
java.lang.NullPointerException: Cannot invoke "FeignMethodInfoDTO.getMethod()"
because "methodInfo" is null解决方案:
java
// 检查方法名是否正确
FeignClassInfoDTO classInfo = FeignRegistryInitializer.registry.get(className);
Map<String, FeignMethodInfoDTO> methods = classInfo.getMethodInfos();
System.out.println("可用的方法: " + methods.keySet());
// 使用正确的方法名(区分大小写)
String correctMethodName = "getDocumentLibrariesByTaskExecutionId";6.2 调试技巧
开启详细日志
yaml
# application.yml
logging:
level:
com.pig4cloud.pigx.ai.agent.langgraph.nodeAction: DEBUG
com.pig4cloud.pigx.ai.util: DEBUG
com.pig4cloud.pigx.ai.config: DEBUG打印注册表信息
java
@Component
public class FeignRegistryDebugger {
@PostConstruct
public void printRegistry() {
System.out.println("========== Feign Registry Info ==========");
FeignRegistryInitializer.registry.forEach((className, classInfo) -> {
System.out.println("服务: " + className);
classInfo.getMethodInfos().forEach((methodName, methodInfo) -> {
System.out.println(" 方法: " + methodName);
System.out.println(" 描述: " + methodInfo.getDescription());
System.out.println(" 摘要: " + methodInfo.getSummary());
});
});
System.out.println("=========================================");
}
}七、注意事项
7.1 线程安全
- 注册表使用
ConcurrentHashMap实现,支持并发读取 MethodInvoker实例不是线程安全的,不要在多线程间共享
7.2 性能考虑
- 反射调用有一定性能开销,不适合超高频调用场景(>10000 QPS)
- 建议在智能体编排、定时任务等场景使用
7.3 异常处理
- 所有异常都会被捕获并转换为标准错误格式
- 业务代码应该检查返回结果中的
success字段 - 原始异常信息会记录到日志中
7.4 参数校验
- 框架会自动校验参数数量、类型、Nullable约束
- 建议在 Feign 接口定义中明确标注参数约束
- 复杂对象参数建议使用 DTO 并添加 JSR-303 校验注解
八、版本历史
v1.0.0 (2024-11-14)
- ✅ 初始版本发布
- ✅ 支持基本类型和包装类型转换
- ✅ 支持复杂对象 JSON 转换
- ✅ 集成 LangGraph 工作流
- ✅ 完善的错误处理和日志记录
九、后续规划
- [ ] 支持异步调用模式
- [ ] 增加调用缓存机制
- [ ] 支持熔断降级
- [ ] 支持重试策略
- [ ] 提供图形化的服务调用监控面板
十、技术支持
如遇到问题,请按以下步骤处理:
- 查看本文档的"故障排查"章节
- 检查日志输出,开启 DEBUG 级别日志
- 联系框架维护人员
维护人员: Xuejx 联系方式: 931956648@qq.com