
本文介绍如何使用 ArchUnit 编写静态分析规则,确保所有调用第三方类 Hello.A() 的代码位置(如方法或构造函数)也必须直接调用 Hello.B(),从而在编译期捕获潜在的逻辑遗漏。
本文介绍如何使用 archunit 编写静态分析规则,确保所有调用第三方类 `hello.a()` 的代码位置(如方法或构造函数)也必须直接调用 `hello.b()`,从而在编译期捕获潜在的逻辑遗漏。
在微服务或大型遗留系统中,第三方库(如示例中的 Hello 类)常存在隐式契约:例如“调用 A() 时必须配套调用 B() 才能保证状态一致性”。这类逻辑若仅靠人工审查或运行时断言极易遗漏。ArchUnit 提供了基于字节码的静态分析能力,可在构建阶段强制校验此类调用约束。
核心思路是:不检查 Hello.A() 内部是否调用了 B(),而是检查所有 调用 Hello.A() 的地方(即 caller),是否在同一代码单元(如某个方法体)中也调用了 Hello.B()。这正是 JavaCodeUnit.getCallsFromSelf() 所描述的“本代码单元发起的所有方法调用”。
以下为完整可运行的 ArchUnit 规则实现:
import static com.tngtech.archunit.base.DescribedPredicate.describe;
import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.target;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaAccess;
import com.tngtech.archunit.core.domain.JavaCodeUnit;
import com.tngtech.archunit.lang.ArchRule;
public class HelloMethodCoCallRule {
public static final ArchRule A_MUST_BE_ACCOMPANIED_BY_B = methods()
.that()
.areDeclaredIn(Hello.class)
.and().haveName("A")
.should()
.onlyBeCalled()
.byCodeUnitsThat(
describe("call Hello.B()", (JavaCodeUnit codeUnit) ->
codeUnit.getCallsFromSelf().stream()
.map(JavaAccess::getTarget)
.anyMatch(target ->
target.getOwner().isEquivalentTo(Hello.class)
&& "B".equals(target.getName())
&& target.getParameterTypes().isEmpty()
)
)
);
}
? 关键说明与注意事项:
- ✅ getCallsFromSelf() 返回的是该代码单元(如一个方法)内部显式发出的所有方法调用,不包含间接调用(如 A() → X() → B())。若需支持间接调用链,需结合 DependencyCycleRule 或自定义递归解析,但会显著增加复杂度与误报率。
- ✅ isEquivalentTo(Hello.class) 比 equals() 更健壮,可正确处理不同类加载器下的类型等价性。
- ⚠️ 该规则假设 B() 是无参方法;若 B 有重载,请根据实际 target.getParameterTypes() 进行精确匹配(例如 target.getParameterTypes().size() == 1 && ...)。
- ⚠️ 规则作用于“调用点”,因此 hello1.A() 单独出现会失败,而 hello2.A(); hello2.B(); 在同一方法内则通过——这正符合题设要求。
最后,在测试类中启用该规则即可:
@Test
public void hello_A_must_always_be_accompanied_by_B() {
JavaClasses importedClasses = new ClassFileImporter()
.importPackages("com.example.yourpackage");
A_MUST_BE_ACCOMPANIED_BY_B.check(importedClasses);
}
通过此方式,你将把运行时风险前置为编译期保障,大幅提升对第三方库隐式契约的管控力。










