ArchUnit架构测试:守护项目架构规范
请详细解释ArchUnit(架构测试框架)的用法。如何用ArchUnit保护包依赖关系、分层架构、循环依赖、命名规范等?ArchUnit与传统静态分析工具有什么不同?给出常见的架构规则示例。
回答
我还是少年
ArchUnit介绍
ArchUnit是一个架构测试框架,在JUnit测试中验证代码架构是否符合预期规则。不同于PMD/SonarQube的泛化规则,ArchUnit可以定义项目特有的架构约束。
常见规则示例
@AnalyzeClasses(packages = "com.example.app")
class ArchitectureTest {
// 1. 分层架构:controller→service→repository
@ArchTest
static final ArchRule layerRule = layeredArchitecture()
.consideringAllDependencies()
.layer("Controller").definedBy("..controller..")
.layer("Service").definedBy("..service..")
.layer("Repository").definedBy("..repository..")
.whereLayer("Controller").mayNotBeAccessedByAnyLayer()
.whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
.whereLayer("Repository").mayOnlyBeAccessedByLayers("Service");
// 2. 禁止循环依赖
@ArchTest
static final ArchRule noCycles = slices()
.matching("..(controller|service|repository)..")
.should().beFreeOfCycles();
// 3. 包依赖约束
@ArchTest
static final ArchRule utilShouldNotDependOnService = classes()
.that().resideInAPackage("..util..")
.should().onlyDependOnClassesThat()
.resideInAnyPackage("..util..", "java..", "org.apache.commons..");
// 4. 命名规范
@ArchTest
static final ArchRule serviceNaming = classes()
.that().areAnnotatedWith(Service.class)
.should().haveSimpleNameEndingWith("Service");
// 5. 禁止特定调用
@ArchTest
static final ArchRule noJpaInController = noClasses()
.that().resideInAPackage("..controller..")
.should().dependOnClassesThat()
.resideInAnyPackage("..javax.persistence..", "..jpa..");
// 6. 特定注解约束
@ArchTest
static final ArchRule noFieldInjection = noFields()
.should().beAnnotatedWith(Autowired.class);
}
与静态分析工具的区别
| 维度 | ArchUnit | SonarQube/PMD |
|---|---|---|
| 定制性 | 极高,项目特有规则 | 通用规则 |
| 运行阶段 | 测试时(单元测试) | 构建时或CI |
| 粒度 | 包/类/方法/字段 | 代码行级 |
| 学习成本 | 较高 | 较低 |
| 典型场景 | 分层架构、DDD限界上下文 | 空指针、命名规范 |
最佳实践:ArchUnit + Checkstyle + SpotBugs组合使用,覆盖不同维度的代码质量。