You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用jQAssistant识别微服务及多类客户端的REST调用关联?

基于jQAssistant与Cypher实现微服务调用关系匹配

前提准备:提取REST服务提供者的URI元数据

先通过已识别的@RestController提取所有服务端点的完整URI模板(包含请求方法与路径),为后续匹配做基础:

MATCH (c:Spring:RestController)-[:DECLARES]->(m:Method)
WHERE EXISTS(m.httpMethod) AND EXISTS(m.requestMapping)
WITH c, m, 
     apoc.text.join(COALESCE(m.requestMapping.value, []), ',') AS path,
     m.httpMethod AS method
MERGE (endpoint:REST:Endpoint {
  serviceName: c.type.fqn,
  fullUri: apoc.text.join([method, path], ' '),
  method: method,
  pathTemplate: path
})
MERGE (c)-[:EXPOSES]->(endpoint)

1. 匹配Spring/GitHub OpenFeign客户端调用

Feign接口通过@FeignClient定义,方法上的@RequestMapping直接指定调用URI,可直接关联:

// 提取Feign客户端的调用端点
MATCH (feign:Spring:FeignClient)-[:DECLARES]->(m:Method)
WHERE EXISTS(m.httpMethod) AND EXISTS(m.requestMapping)
WITH feign, m, 
     apoc.text.join(COALESCE(m.requestMapping.value, []), ',') AS callPath,
     m.httpMethod AS callMethod
MERGE (caller:REST:ClientCall {
  clientName: feign.type.fqn,
  callUri: apoc.text.join([callMethod, callPath], ' '),
  method: callMethod,
  pathTemplate: callPath
})
MERGE (feign)-[:MAKES_CALL]->(caller)

// 匹配提供者与消费者,创建INVOKES_REMOTE关系
MATCH (caller:REST:ClientCall), (endpoint:REST:Endpoint)
WHERE caller.method = endpoint.method 
  AND apoc.text.match(caller.pathTemplate, endpoint.pathTemplate)
MERGE (caller)-[:INVOKES_REMOTE]->(endpoint)

2. 匹配RestTemplate/OAuthRestTemplate调用

RestTemplate通过代码硬编码URI,需提取方法调用中的字符串参数或变量,同时从方法名推断请求方法:

// 提取RestTemplate的URI调用
MATCH (m:Method)-[:INVOKES]->(rm:Method {name: ["getForObject", "postForObject", "exchange"]})
WHERE rm.declaringType.fqn CONTAINS "RestTemplate"
WITH m, rm, 
     [param IN rm.parameters WHERE param.type.fqn = "java.lang.String"][0] AS uriParam,
     [arg IN m.arguments WHERE arg.type.fqn = "java.lang.String"][0] AS uriArg
WITH m, 
     COALESCE(uriArg.value, uriParam.name) AS callPath,
     CASE rm.name 
       WHEN "getForObject" THEN "GET"
       WHEN "postForObject" THEN "POST"
       WHEN "exchange" THEN [arg IN m.arguments WHERE arg.type.fqn CONTAINS "HttpMethod"][0].value
       ELSE "UNKNOWN"
     END AS callMethod
MERGE (caller:REST:ClientCall {
  clientName: m.declaringType.fqn,
  callUri: apoc.text.join([callMethod, callPath], ' '),
  method: callMethod,
  pathTemplate: callPath
})
MERGE (m.declaringType)-[:MAKES_CALL]->(caller)

// 匹配关联
MATCH (caller:REST:ClientCall), (endpoint:REST:Endpoint)
WHERE caller.method = endpoint.method 
  AND apoc.text.match(caller.pathTemplate, endpoint.pathTemplate)
MERGE (caller)-[:INVOKES_REMOTE]->(endpoint)

3. 匹配Apache HttpClient调用

HttpClient通过HttpGet/HttpPost等构造方法传入URI,提取构造参数并匹配请求方法:

// 提取HttpClient的URI调用
MATCH (m:Method)-[:NEW]->(req:Type {fqn: ["org.apache.http.client.methods.HttpGet", "org.apache.http.client.methods.HttpPost"]})
WITH m, req, 
     [arg IN m.arguments WHERE arg.type.fqn = "java.lang.String"][0] AS uriArg
WITH m, 
     uriArg.value AS callPath,
     CASE req.fqn 
       WHEN "org.apache.http.client.methods.HttpGet" THEN "GET"
       WHEN "org.apache.http.client.methods.HttpPost" THEN "POST"
       ELSE "UNKNOWN"
     END AS callMethod
MERGE (caller:REST:ClientCall {
  clientName: m.declaringType.fqn,
  callUri: apoc.text.join([callMethod, callPath], ' '),
  method: callMethod,
  pathTemplate: callPath
})
MERGE (m.declaringType)-[:MAKES_CALL]->(caller)

// 匹配关联
MATCH (caller:REST:ClientCall), (endpoint:REST:Endpoint)
WHERE caller.method = endpoint.method 
  AND apoc.text.match(caller.pathTemplate, endpoint.pathTemplate)
MERGE (caller)-[:INVOKES_REMOTE]->(endpoint)

4. 匹配OkHttpClient调用

OkHttpClient通过Request.Builder的url()方法传入URI,提取参数并从method()调用推断请求方法(默认GET):

// 提取OkHttpClient的URI调用
MATCH (m:Method)-[:INVOKES]->(urlMethod:Method {name: "url", declaringType: "okhttp3.Request$Builder"})
WITH m, urlMethod, 
     [arg IN urlMethod.arguments WHERE arg.type.fqn = "java.lang.String"][0] AS uriArg
WITH m, 
     uriArg.value AS callPath,
     COALESCE(
       [methodCall IN m.invokes WHERE methodCall.name = "method" AND methodCall.declaringType = "okhttp3.Request$Builder"][0].arguments[0].value,
       "GET"
     ) AS callMethod
MERGE (caller:REST:ClientCall {
  clientName: m.declaringType.fqn,
  callUri: apoc.text.join([callMethod, callPath], ' '),
  method: callMethod,
  pathTemplate: callPath
})
MERGE (m.declaringType)-[:MAKES_CALL]->(caller)

// 匹配关联
MATCH (caller:REST:ClientCall), (endpoint:REST:Endpoint)
WHERE caller.method = endpoint.method 
  AND apoc.text.match(caller.pathTemplate, endpoint.pathTemplate)
MERGE (caller)-[:INVOKES_REMOTE]->(endpoint)

循环依赖排查

完成所有调用关系匹配后,可用以下查询检测微服务间的循环依赖:

MATCH path = (s:Spring:RestController)-[:EXPOSES]->()-[:INVOKES_REMOTE*1..]->()-[:EXPOSES]-(s)
RETURN path, length(path) AS cycleLength
ORDER BY cycleLength DESC

内容的提问来源于stack exchange,提问作者Venkat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 12:21:03