如何在jQAssistant中区分直接依赖与间接依赖
Great question! Let’s unpack this about jQAssistant’s dependency behavior and how to distinguish the two types of dependencies you’re asking about.
1. Is this behavior intentional in jQAssistant?
Absolutely—this is by design. jQAssistant analyzes dependencies based on compiled bytecode, not just the import statements in your source code. In your example, even though the Service class doesn’t explicitly import org.example.domain.Product, the compiled bytecode includes a reference to Product.getId() because transaction.getProduct() returns a Product instance, and the JVM needs to resolve that type to execute the method call.
jQAssistant’s goal is to capture all dependencies that exist at runtime, not just the ones you explicitly declare in imports. That’s why it creates a :DEPENDS_ON relationship between Service and Product here—it’s reflecting the actual runtime dependency that would exist when the code runs.
2. How to distinguish direct vs. indirect dependencies?
Yes, you can differentiate between direct dependencies (explicitly imported/used types) and indirect dependencies (types referenced via method call chains) by leveraging jQAssistant’s granular relationship types and properties. Here’s how:
First, clarify the definitions
- Direct Dependencies: Types your class explicitly references directly, like:
- Fields declared with the target type
- Method parameters or return values of the target type
- Explicit instantiation (e.g.,
new Product()) - Types explicitly imported in the source code (tracked via jQAssistant’s
:IMPORTrelationship)
- Indirect Dependencies: Types you access only through another object’s methods (like your
transaction.getProduct().getId()example)
Example Cypher Queries
Query for direct dependencies
This query captures explicit references your Service class has to domain package types:
MATCH (service:Class {name:"Service"}) // Capture explicit imports OPTIONAL MATCH (service)-[:IMPORT]->(directDep:Class) // Capture fields/methods that reference the type directly OPTIONAL MATCH (service)-[:DECLARES_FIELD|DECLARES_METHOD]->(member) OPTIONAL MATCH (member)-[:TYPE]->(directDep:Class) WHERE directDep.fqn STARTS WITH "org.example.domain" RETURN DISTINCT service.fqn, directDep.fqn AS direct_dependency
Query for indirect dependencies
This query targets dependencies that come from method call chains, excluding any already captured as direct dependencies:
MATCH (service:Class {name:"Service"})-[:INVOKES]->(method:Method) MATCH (method)-[:RETURNS]->(returnType:Class) MATCH (returnType)-[:DECLARES_METHOD]->(targetMethod:Method) MATCH (targetMethod)-[:RETURNS]->(indirectDep:Class) WHERE indirectDep.fqn STARTS WITH "org.example.domain" // Exclude direct dependencies to avoid overlap AND NOT EXISTS((service)-[:IMPORT|TYPE]->(indirectDep)) RETURN service.fqn, indirectDep.fqn AS indirect_dependency
Alternative: Use dependency source properties
jQAssistant’s :DEPENDS_ON relationship often includes a source property that tells you why the dependency exists (e.g., METHOD_INVOCATION for indirect calls, FIELD_TYPE for direct field references). You can use this to filter results directly:
MATCH (c {name:"Service"})-[:DEPENDS_ON]->(d) WHERE d.fqn STARTS WITH "org.example.domain" RETURN c.fqn, d.fqn, (c)-[:DEPENDS_ON]->d.source AS dependency_source
Look for values like METHOD_INVOCATION for indirect dependencies, and FIELD_TYPE, METHOD_PARAMETER, or IMPORT for direct ones.
内容的提问来源于stack exchange,提问作者Moe

