
本文介绍如何使用 ArchUnit 编写静态分析规则,确保所有调用 Hello.A() 的代码单元(如方法或构造器)必须同时调用 Hello.B(),适用于第三方库中不可修改的类(如 Hello),无需运行时执行。
本文介绍如何使用 archunit 编写静态分析规则,确保所有调用 `hello.a()` 的代码单元(如方法或构造器)必须同时调用 `hello.b()`,适用于第三方库中不可修改的类(如 `hello`),无需运行时执行。
在架构约束测试中,常需保障跨方法的调用契约(例如“调用了 A() 就不得遗漏 B()”),尤其当目标类来自第三方库、无法通过注解或模板强制约束时。ArchUnit 的静态字节码分析能力恰好胜任此类场景——它不依赖运行时行为,而是直接解析 .class 文件中的调用关系。
核心思路是:对每一个声明或调用 Hello.A() 的代码单元(JavaCodeUnit),检查其自身发起的所有方法调用中是否至少包含一次对 Hello.B() 的调用。这可通过自定义 DescribedPredicate<JavaCodeUnit> 实现:
import static com.tngtech.archunit.base.DescribedPredicate.describe;
import com.tngtech.archunit.core.domain.JavaCodeUnit;
import com.tngtech.archunit.core.domain.JavaAccess;
DescribedPredicate<JavaCodeUnit> callsHelloB = describe("call Hello.B()", codeUnit ->
codeUnit.getCallsFromSelf().stream()
.map(JavaAccess::getTarget)
.anyMatch(target ->
target.getOwner().isEquivalentTo(Hello.class) // 所属类为 Hello
&& "B".equals(target.getName()) // 方法名为 B
&& target.getParameterTypes().isEmpty() // 无参(按需调整)
)
);随后将其集成到 ArchUnit 规则中:
ArchRule rule = methods()
.that().areDeclaredIn(Hello.class).and().haveName("A")
.should().onlyBeCalled().byCodeUnitsThat(callsHelloB);
rule.check(classes); // 执行校验⚠️ 注意事项:
- getCallsFromSelf() 仅返回该代码单元直接发起的调用(不包含间接调用链),因此本规则保证的是「显式共现」,而非逻辑上的调用依赖;
- 若 Hello.B() 有重载,请根据 target.getParameterTypes() 精确匹配签名;
- isEquivalentTo(Hello.class) 比 equals() 更健壮,可正确处理不同类加载器或泛型擦除场景;
- 此规则作用于调用方(caller),而非 Hello 类内部 —— 因此它能捕获 hello1.A()(失败)与 hello2.A(); hello2.B()(成功)等真实用例差异。
总结:通过组合 getCallsFromSelf() 与精准的目标方法匹配,ArchUnit 可高效实现跨方法的调用完整性断言,是保障遗留系统或第三方集成中隐式契约的可靠手段。

















