全面掌握软件架构的守护神-ArchUnit

开发 前端
ArchUnit 通过分析给定的 Java 字节码,将所有类导入到 Java 代码结构中,来检查包、类、层、切片上依赖关系,包括对循环依赖关系等问题的检查。

简要介绍

ArchUnit 是一个免费、简单和可扩展的库,可以使用任何普通的 Java 单元测试框架检查 Java 代码的架构和编码规则。

基本原理

ArchUnit 通过分析给定的 Java 字节码,将所有类导入到 Java 代码结构中,来检查包、类、层、切片上依赖关系,包括对循环依赖关系等问题的检查。

版本分支

ArchUnit 于2017年4月23日发布第一个版本,2022年10月3日发布了 1.0.0 版本,共32次Release。

ArchUnitNet 是一个 关于.NET/C# 的架构测试工具。

体系结构

  • 总览

ArchUnit 由 ArchUnit、 ArchUnit-junit4、ArchUnit-junit5-api、 ArchUnit-junit5-engine 和
ArchUnit-junit5-engine-api 等模块组成,还为最终用户提供了 archunit-example 模块。

  • ArchUnit

ArchUnit 模块包含编写架构测试所需的核心基础结构,如ClassFileImporter、域对象和规则语法结构。ArchUnit 分为 Core、Lang 和 Library 三层,Core 层处理基本的基础结构,比如将字节码导入为Java对象; Lang 层提供以简洁的方式制定架构规则的语法; Library 层包含更为复杂的预定义规则,如多层分层架构。

  • ArchUnit-Junit

ArchUnit-junit4 模块包含与 JUnit 4集成的基础结构,特别是用于缓存导入类的 ArchUnitRunner。

ArchUnit-junit5-* 模块包含与 JUnit 5集成的基础结构,并包含在测试运行之间缓存导入类的基础结构。ArchUnit-junit5-API 包含用户 API,用于编写支持 ArchUnit 的 JUnit 5的测试,ArchUnit-junit5-engine 包含运行这些测试的运行时引擎。
ArchUnit-junit5-engine-API 包含一些 API 代码,这些 API 代码用于那些想要对运行 ArchUnit JUnit 5测试进行更详细控制的工具,特别是一个 FieldSelector,它可以用来指示 ArchUnitTestEngine 运行一个特定的规则字段(比较 JUnit 4和5 Support)。

  • ArchUnit-Example

archunit-example 模块包含违反这些规则的示例体系结构规则和示例代码。在这里可以找到关于如何为项目设置规则的灵感,或者在 ArchUnit-最新发布版本的示例。

  • ArchUnit-Maven-Plugin

有一个maven插件arch-unit-maven-plugin,可以从 Maven 运行 ArchUnit 规则。

安装导入

要使用 ArchUnit,在类路径中包含相应的 JAR 文件就足够了。

# junit4 maven 依赖,for junit4
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit4</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>

# junit5 maven 依赖,for junit5
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>

快速体验
@RunWith(ArchUnitRunner.class) // Junit5不需要这行
@AnalyzeClasses(packages = "com.mycompany.myapp") // ① 导入要分析的类
public class MyArchitectureTest {

@ArchTest // ② 方式一:使用静态字段,对要分析的类的架构规则进行断言
public static final ArchRule myRule = classes()
.that().resideInAPackage("..service..")
.should().onlyBeAccessed().byAnyPackage("..controller..", "..service..");

@Test // ② 方式二:使用方法,并自行导入类,对要分析的类的架构规则进行断言
public void Services_should_only_be_accessed_by_Controllers(){
JavaClasses importedClasses = new ClassFileImporter()
.importPackages("com.mycompany.myapp");

ArchRule myRule = classes()
.that().resideInAPackage("..service..")
.should().onlyBeAccessed().byAnyPackage("..controller..", "..service..");

myRule.check(importedClasses);
}
}

详细功能

  • 包依赖检查
// 不允许任何 source 包中的类依赖于 foo 包中的类
noClasses().that().resideInAPackage("..source..")
.should().dependOnClassesThat().resideInAPackage("..foo..");

// foo 包中的类只能被 source.one 包和本包中的类依赖
classes().that().resideInAPackage("..foo..")
.should().onlyHaveDependentClassesThat()
.resideInAnyPackage("..source.one..", "..foo..")
  • 类依赖检查

// 名为 *Bar 的类只能被名为 Bar 的类依赖 
classes().that().haveNameMatching(".*Bar")
.should().onlyHaveDependentClassesThat().haveSimpleName("Bar")

  • 类容器检查

// Foo 开头的类只能放在 com.foo 包下
classes().that().haveSimpleNameStartingWith("Foo")
.should().resideInAPackage("com.foo")

  • 类继承检查
// 实现 Connection 接口的类名称只能以 Connection 结尾
classes().that().implement(Connection.class)
.should().haveSimpleNameEndingWith("Connection")

// 用到 EntityManager 的类只能在 persistence 包下
classes().that().areAssignableTo(EntityManager.class)
.should().onlyHaveDependentClassesThat()
.resideInAnyPackage("..persistence..")
  • 注解检查
// 用到 EntityManager 的类需要依赖于 Transactional 注解
classes().that().areAssignableTo(EntityManager.class)
.should().onlyHaveDependentClassesThat()
.areAnnotatedWith(Transactional.class)
  • 分层检查

layeredArchitecture()
.consideringAllDependencies()
.layer("Controller").definedBy("..controller..")
.layer("Service").definedBy("..service..")
.layer("Persistence").definedBy("..persistence..")

.whereLayer("Controller").mayNotBeAccessedByAnyLayer() // controller层不能被其它层访问
.whereLayer("Service").mayOnlyBeAccessedByLayers("Controller") // service层只能被controller层访问
.whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service") // persistence层只能被service层访问

  • 循环依赖检查

// com.myapp 的直属子包间不能存在循环依赖
slices().matching("com.myapp.(*)..").should().beFreeOfCycles()

深入了解

  • 导入

//使用预定义导入选项从classpath导入类
new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS)
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importClasspath();

//从文件路径导入类
JavaClasses classes = new ClassFileImporter().importPath("/some/path/to/classes");

// 自定义导入选项,以忽略测试类
ImportOption ignoreTests = new ImportOption() {
@Override
public boolean includes(Location location){
return !location.contains("/test/"); // ignore any URI to sources that contains '/test/'
}
};
// 使用自定义规则从classpath导入类
JavaClasses classes = new ClassFileImporter()
.withImportOption(ignoreTests).importClasspath();
  • 概念模型

大多数对象类似于 Java 反射 API,包括继承关系。因此,一个 JavaClass 具有一些 JavaMember,JavaMember 可以是 JavaField、 JavaMethod、 JavaConstruction (或 JavaStaticInitializer)。

CodeUnit 虽然不存在于反射 API 中,但是为任何可以访问其他代码的东西引入一个概念是有意义的。它要么是一个方法,一个构造函数(包括类初始化器) ,要么是一个类的静态初始化器(例如静态块,静态字段赋值,等等)。

对另一个类的访问也是一个不在反射范畴的概念,ArchUnit在最细粒度上,只能从 CodeUnit 通过 JavaFieldAccess 、JavaMethodCall、JavaConstructorCall 来分别访问字段、方法或构造函数。

由于被访问的字段、方法、构造函数可能定义在超类中,所以引入了FieldAccessTarget、MethodCallTarget、ConstructorCallTarget等Target系列概念,用于解析到真正的目标类。

由于导入的类集并不总是包含所有的类,所以上图中resolves to可能解析到0个对象。

另外,上图中MethodCallTarget可以resolves to多个JavaMethod,其原因在于某个方法可能实现了多个接口,如下图所示。

  • Core API 和 Lang API

Core API具备强大的功能,但是Lang API更为简洁。

// 本段代码为使用Core API断言规则
Set<JavaClass> services = new HashSet<>();
for (JavaClass clazz : classes) {
// choose those classes with FQN with infix '.service.'
if (clazz.getName().contains(".service.")) {
services.add(clazz);
}
}

for (JavaClass service : services) {
for (JavaAccess<?> access : service.getAccessesFromSelf()) {
String targetName = access.getTargetOwner().getName();

// fail if the target FQN has the infix ".controller."
if (targetName.contains(".controller.")) {
String message = String.format(
"Service %s accesses Controller %s in line %d",
service.getName(), targetName, access.getLineNumber());
Assert.fail(message);
}
}
}

// 如下代码片段为使用Lang API实现如上相同的规则断言
ArchRule rule = ArchRuleDefinition.noClasses()
.that().resideInAPackage("..service..")
.should().accessClassesThat().resideInAPackage("..controller..");

rule.check(importedClasses);

// 如下代码展示Lang API提供的 and、or 等组合功能
noClasses()
.that().resideInAPackage("..service..")
.or().resideInAPackage("..persistence..")
.should().accessClassesThat().resideInAPackage("..controller..")
.orShould().accessClassesThat().resideInAPackage("..ui..")

rule.check(importedClasses);

Lang 层除了为类提供API之外,还为其成员提供了正反两个系列的API,包括members()、noMembers()、fields()、noFields()、codeUnits()、noCodeUnits()、constructors()、noConstructors()等。

// 如下代码片段展示与成员方法有关的API
ArchRule rule = ArchRuleDefinition.methods()
.that().arePublic()
.and().areDeclaredInClassesThat().resideInAPackage("..controller..")
.should().beAnnotatedWith(Secured.class);

rule.check(importedClasses);

  • 自定义规则

在ArchUnit,大多数规则都是如下架构。

classes that ${PREDICATE} should ${CONDITION}

如果预定义API不能满足要求,可以自定义规则。

// 定义一个 Predicate
DescribedPredicate<JavaClass> haveAFieldAnnotatedWithPayload =
new DescribedPredicate<JavaClass>("have a field annotated with @Payload"){
@Override
public boolean apply(JavaClass input){
boolean someFieldAnnotatedWithPayload = // iterate fields and check for @Payload
return someFieldAnnotatedWithPayload;
}
};

// 定义一个Condition
ArchCondition<JavaClass> onlyBeAccessedBySecuredMethods =
new ArchCondition<JavaClass>("only be accessed by @Secured methods") {
@Override
public void check(JavaClass item, ConditionEvents events){
for (JavaMethodCall call : item.getMethodCallsToSelf()) {
if (!call.getOrigin().isAnnotatedWith(Secured.class)) {
String message = String.format(
"Method %s is not @Secured", call.getOrigin().getFullName());
events.add(SimpleConditionEvent.violated(call, message));
}
}
}
};

// 对类集应用 Predicate 和 Condition
classes().that(haveAFieldAnnotatedWithPayload).should(onlyBeAccessedBySecuredMethods);

  • 控制规则文案

// 对于不常见的规则,最好按照如下方法记录其理由
classes().that(haveAFieldAnnotatedWithPayload)
.should(onlyBeAccessedBySecuredMethods)
.because("@Secured methods will be intercepted, checking for increased privileges " +
"and obfuscating sensitive auditing information");

// 如果规则复杂,且自动生成的规则文本太复杂,可以使用如下方式完全覆盖规则说明
classes().that(haveAFieldAnnotatedWithPayload)
.should(onlyBeAccessedBySecuredMethods)
.as("Payload may only be accessed in a secure way");

  • 忽略违规情况

因遗留代码或其它无法满足规则的情况,可以将一个名为
archunit_ignore_patterns.txt 的文本文件放在classpath的根目录下,并在每一行使用一个可以匹配要忽略的冲突的正则表达式。

# 这里可以写上忽略的原因
.*some\.pkg\.LegacyService.*

  • 架构检查

ArchUnit 在 Library 层预定义了若干架构检查的 API。目前可以方便地检查分层架构和洋葱架构,将来可能会扩展到管道、过滤器,以及业务和技术关注点分离等。

// 架构检查的入口点
com.tngtech.archunit.library.Architectures

// 以下是对分层架构的检查示例
layeredArchitecture()
.consideringAllDependencies()
.layer("Controller").definedBy("..controller..")
.layer("Service").definedBy("..service..")
.layer("Persistence").definedBy("..persistence..")

.whereLayer("Controller").mayNotBeAccessedByAnyLayer()
.whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
.whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service")

// 以下为对洋葱架构(又称六边形架构、端口和适配器架构)的检查示例
onionArchitecture()
.domainModels("com.myapp.domain.model..")
.domainServices("com.myapp.domain.service..")
.applicationServices("com.myapp.application..")
.adapter("cli", "com.myapp.adapter.cli..")
.adapter("persistence", "com.myapp.adapter.persistence..")
.adapter("rest", "com.myapp.adapter.rest..");

  • 切片检查
// 切片检查的入口点
com.tngtech.archunit.library.dependencies.SlicesRuleDefinition

// 检查 myapp 包的下一级子包中的类不存在循环依赖
SlicesRuleDefinition.slices()
.matching("..myapp.(*)..")
.should().beFreeOfCycles()

// 检查 myapp 包的所有子包中的类不存在循环依赖
SlicesRuleDefinition.slices()
.matching("..myapp.(**)")
.should().notDependOnEachOther()

// 检查 myapp 包和 service 包之间的包中的类不存在相互依赖情况
SlicesRuleDefinition.slices()
.matching("..myapp.(**).service..")
.should().notDependOnEachOther()

如果以上切片不能满足要求,还可以使用SliceAssignment类来定制切片。

  • 编码检查

ArchUnit 通过 GeneralCodingRules 类提供了一组通用性较高的编码规则检查。

  • 依赖检查

DependencyRules 类提供了一组检查类之间依赖关系的规则和条件。

  • 代理检查

ProxyRules 提供了关于使用代理对象的检查。

  • 基于PlantUML检查

ArchUnit 在
com.tngtech.archunit.library.plantuml 包下提供了一个支持 PlantUML 的特性,用于直接从 PlantUML 派生出检查规则,对相应的类进行检查。

URL myDiagram = getClass().getResource("my-diagram.puml");

classes().should(
adhereToPlantUmlDiagram(myDiagram,
consideringAllDependencies())
);

支持的UML只能是组件图,用Java类的Package作为组件原型。ArchUnit对组件图还有一些特殊要求,同时提供一些检查的额外选项。

' 如果使用如下组件图进行检查,target中的类依赖source中的类将违反规则
@startuml
[某个源组件] <<..some.source..>>
[某个目标组件] <<..some.target..>> as target

[某个源组件] --> target
@enduml

  • 冻结

当违规行为过多,无法立即修复时,需要建立一种迭代机制,防止代码基线进一步恶化。

ArchUnit 的 FreezingArchRule 类提供这方面的帮助,将现有违规行为记录到 ViationStore 中,然后后续检查只报告新增的违规行为,并忽略已知的违规。一旦违规得到修复,FreezingArchRule 将自动将其从已知冲突中去除,无需额外回归。代码行号的变化不会影响违规行为。

// 冻结某个规则
ArchRule rule = FreezingArchRule
.freeze(classes().should()./*complete ArchRule*/);

FreezingArchRule 默认使用一个简单的纯文本文件保存 ViationStore,以便利用 VCS 进行跟踪管理。该文件的路径包括 ViationStore 的创建和更新行为也是可配置的,方便用于CI环境。

# ViationStore 文件
freeze.store.default.path=/some/path/in/a/vcs/repo
# 是否允许创建 ViationStore,默认为 false
freeze.store.default.allowStoreCreatinotallow=true
# 是否允许更新 ViationStore,默认为 true
freeze.store.default.allowStoreUpdate=false

# 是否允许重新冻结所有违规行为,表示随时接受新的违规而只报告成功,默认为 false
freeze.refreeze=true

# 支持自定义冻结存储(继承com.tngtech.archunit.library.freeze.ViolationStore
freeze.store=fully.qualified.name.of.MyCustomViolationStore
# 如下行用于为自定义存储类设置属性
freeze.store.propOne=valueOne
freeze.store.propTwo=valueTwo

# 支持自定义违规行匹配器
freeze.lineMatcher=fully.qualified.name.of.MyCustomLineMatcher
  • 度量

与代码质量度量(如圈复杂度或方法长度)类似,软件架构度量力求度量软件的结构和设计。

ArchUnit 可以用来计算一些众所周知的软件体系结构度量。

import com.tngtech.archunit.library.metrics.ArchitectureMetrics;
// ...

JavaClasses classes = // ...
Set<JavaPackage> packages = classes.getPackage("com.example").getSubpackages();

// These components can also be created in a package agnostic way, compare MetricsComponents.from(..)
MetricsComponents<JavaClass> components = MetricsComponents.fromPackages(packages);

// 计算 John Lakos 提出的依赖度量指标,指示系统组件间依赖程度
LakosMetrics metrics = ArchitectureMetrics.lakosMetrics(components);
// CCD 累积组件依赖,加总所有组件所有向外依赖数
System.out.println("CCD: " + metrics.getCumulativeComponentDependency());
// ACD 平均组件依赖,CCD除以组件数
System.out.println("ACD: " + metrics.getAverageComponentDependency());
// RACD 相对平均依赖,ACD除以组件数
System.out.println("RACD: " + metrics.getRelativeAverageComponentDependency());
// NCCD 系统的 CCD 除以具有相同数量成分的平衡二叉搜索树的 CCD
System.out.println("NCCD: " + metrics.getNormalizedCumulativeComponentDependency());

// 计算 Robert C. Martin 提出的度量指标,指示组件之间的耦合度
ComponentDependencyMetrics metrics = ArchitectureMetrics.componentDependencyMetrics(components);
//CE 传出耦合,对任何其它组件的依赖数
System.out.println("Ce: " + metrics.getEfferentCoupling("com.example.component"));
//CA 传入耦合,来自任何其它组件的依赖数
System.out.println("Ca: " + metrics.getAfferentCoupling("com.example.component"));
// I 不稳定性,Ce/(Ca + Ce)
System.out.println("I: " + metrics.getInstability("com.example.component"));
// A 抽象性,组件内抽象类的数量 / 组件中所有类的数量
// 在 ArchUnit 中,抽象值仅基于公共类,即从外部可见的类。
System.out.println("A: " + metrics.getAbstractness("com.example.component"));
// D 距离主序列, | A + I - 1 |, 即距离(A = 1,I = 0)(A = 0,I = 1)之间的理想线的归一化距离
System.out.println("D: " + metrics.getNormalizedDistanceFromMainSequence("com.example.component"));

// 计算 Herbert Dowalil 提出的可见性指标,指示组件的信息隐藏能力
VisibilityMetrics metrics = ArchitectureMetrics.visibilityMetrics(components);
// RV 相对可见性,当前组件中可见元素数量 / 当前组件中所有元素数量
System.out.println("RV : " + metrics.getRelativeVisibility("com.example.component"));
// ARV 平均相对能见度,RV的均值
System.out.println("ARV: " + metrics.getAverageRelativeVisibility());
// GRV 全局相对可见性,所有组件中的可见元素数量 / 所有组件中素有元素数量
System.out.println("GRV: " + metrics.getGlobalRelativeVisibility());

  • JUnit支持

// 以下为基本用法,项目太大时,会因为类的导入导致性能较差,也容易出错
@Test
public void rule1(){
JavaClasses importedClasses = new ClassFileImporter().importClasspath();

ArchRule rule = classes()...

rule.check(importedClasses);
}

// 以下为正常用法
// 缓存基于测试类,同一个测试类中声明的多个规则重用缓存
// 缓存基于导入位置,从相同URI导入时会发生重用,这种形式为软引用,内存不足时会被清除
@RunWith(ArchUnitRunner.class) // 此行JUnit5不需要
@AnalyzeClasses(packages = "com.myapp")
public class ArchitectureTest {

// 可将规则声明为静态字段
@ArchTest
public static final ArchRule rule1 = classes().should()...

@ArchTest
public static final ArchRule rule2 = classes().should()...

@ArchTest
public static void rule3(JavaClasses classes){
// 静态方法,会使用缓存
}

}

  • 控制导入范围

// 控制要导入的类
@AnalyzeClasses(packages = {"com.myapp.subone", "com.myapp.subtwo"})

// 也可以利用具有代表性的类,会导入该类所在包的所有类,这种方式便于重构
@AnalyzeClasses(packagesOf = {SubOneConfiguration.class, SubTwoConfiguration.class})

// 也可以通过实现 LocationProvider 来控制要导入哪些类
public class MyLocationProvider implements LocationProvider {
@Override
public Set<Location> get(Class<?> testClass) {
// Determine Locations (= URLs) to import
// Can also consider the actual test class, e.g. to read some custom annotation
}
}
@AnalyzeClasses(locations = MyLocationProvider.class)

// 可以利用导入选项控制要导入的类。导入选项类也支持自定义。
@AnalyzeClasses(importOptions = {DoNotIncludeTests.class,
DoNotIncludeJars.class})

  • 禁用位置缓存

// 缓存过多的类可能会产生GC延迟。
// 可以通过 CacheMode.PER_CLASS 只启用基于测试类的缓存,
// 而禁用基于位置的缓存
@AnalyzeClasses(packages = "com.myapp.special",
cacheMode = CacheMode.PER_CLASS)

  • 忽略测试

可使用 @ArchIgnore 注解来忽略对规则的检查。

public class ArchitectureTest {

// 会运行
@ArchTest
public static final ArchRule rule1 = classes().should()...

// 不会运行
@ArchIgnore
@ArchTest
public static final ArchRule rule2 = classes().should()...
}
  • 规则分组

可使用如下方式对检查规则进行分组,提升规则的组织性,并允许在项目或模块间复用规则。

public class ServiceRules {
@ArchTest
public static final ArchRule ruleOne = ...

// 其他规则
}

public class PersistenceRules {
@ArchTest
public static final ArchRule ruleOne = ...

// 更多规则
}

@RunWith(ArchUnitRunner.class) // Junit5不需要此行
@AnalyzeClasses
public class ArchitectureTest {

@ArchTest
static final ArchTests serviceRules = ArchTests.in(ServiceRules.class);

@ArchTest
static final ArchTests persistenceRules = ArchTests.in(PersistenceRules.class);

}

  • 生成显示名称

ArchUnit 通过使用空格替换原始规则名称中的下划线,提供了在测试报告中生成更多可读名称的可能性。

// 如果方法或字段的名称为:
some_Field_or_Method_rule
// 则在测试报告中会被替换为:
some Field or Method rule

也可以将下面的属性移除或设置为false来关闭该特性。

junit.displayName.replaceUnderscoresBySpaces=true

高级配置

  • 覆盖默认配置

ArchUnit 会使用 Java 资源标准加载机制来加载classpath根下的 archunit.properties 配置文件。

可以通过将系统属性传递给执行ArchUnit的JVM进程来覆盖 archunit.properties 中的配置。

-Darchunit.propertyName=propertyValue

  • 对缺失类的处理

# 不要从类路径中解析缺失的类依赖,以提升分析性能(默认行为)
resolveMissingDependenciesFromClassPath=false

# 只从类路径解析 some.pkg.[one, two] 两个包的类,其它则使用存根
classResolver=com.tngtech.archunit.core.importer.resolvers.SelectedClassResolverFromClasspath
classResolver.args=some.pkg.one,some.pkg.two

  • 导入不在类路径上的其它源中的类
# 从以下类继承一个自定义类解析器,如:some.pkg.MyCustomClassResolver
com.tngtech.archunit.core.importer.resolvers.ClassResolver

# 在 archunit.properties 中配置自定义类解析器
classResolver=some.pkg.MyCustomClassResolver

# 为自定义类解析器提供所需参数
classResolver.args=myArgOne,myArgTwo
  • 自定义分析迭代次数

// 以下是默认迭代次数,设置为 -1 表示直到解析出所有类型才停止。
// 设置为 0 将禁用自动解析,类信息可能会不完整或错误
// 较大或负值可能对性能产生巨大影响

#字段类型、方法参数类型等成员类型
import.dependencyResolutionProcess.maxIterationsForMemberTypes = 1
# 访问类型,如一个方法调用其它类中的方法
import.dependencyResolutionProcess.maxIterationsForAccessesToTypes = 1
# 超类,如被继承的类、被实现的接口
import.dependencyResolutionProcess.maxIterationsForSupertypes = -1
# 封闭类型,如嵌套类的外部类
import.dependencyResolutionProcess.maxIterationsForEnclosingTypes = -1
# 注解类型,包括注解的参数
import.dependencyResolutionProcess.maxIterationsForAnnotationTypes = -1
# 泛型签名类型,如包含参数化类型的类型
import.dependencyResolutionProcess.maxIterationsForGenericSignatureTypes = -1

  • 控制类 MD5 摘要

# 通过以下方式激活类 MD5(默认为false,以提升性能) ,用于跟踪异常行为
enableMd5InClassSources=true

# 获取类 MD5 的方法如下
javaClass.getSource().get().getMd5sum()

  • 控制空检查
// ArchUnit 默认禁止对一组空类进行检查

# 可以针对单个规则应用 allowEmptyShould(true) 来覆盖对空检查的默认行为
classes().that()...should()...allowEmptyShould(true)

# 允许全局空检查
archRule.failOnEmptyShould=false
  • 控制循环依赖检查

# 限制循环依赖检查最大周期,默认为100
cycles.maxNumberToDetect=50

# 限制每个周期报告的依赖数量(无论如何总是检查所有依赖),只影响所需的堆大小,默认为20
cycles.maxNumberOfDependenciesPerEdge=5

  • 自定义错误消息
# 首先通过继承下面的类来实现一个自定义错误消息的类,如:some.pkg.MyCustomFailureDisplayFormat
com.tngtech.archunit.lang.FailureDisplayFormat

# 然后在archunit.properties中作如下配置
failureDisplayFormat=some.pkg.MyCustomFailureDisplayFormat


责任编辑:武晓燕 来源: 今日头条
相关推荐

2020-06-12 12:51:36

存储

2010-01-20 22:17:00

TrunkVLAN交换机配置

2010-03-17 11:22:01

APC Smart-u

2016-04-22 10:08:29

wifi安全

2020-01-06 10:31:21

物联网Python编程语言

2013-06-26 10:31:03

神十天宫航天信息系统天融信

2021-06-29 21:48:32

开源语言架构

2017-02-10 17:28:16

盛邦安全RSA网络安全

2010-09-03 13:58:02

Ethereal网络协

2009-12-22 16:43:28

2011-04-06 10:27:46

Java异常处理

2023-06-28 11:49:56

Linux命令

2022-04-14 10:10:59

Nginx开源Linux

2021-06-15 21:00:24

架构测试代码化

2020-06-02 16:19:09

华为

2012-05-29 09:20:28

SQL Profile

2023-12-29 15:00:12

漏洞安全人工智能

2024-03-01 01:25:40

结构化日志可读性

2023-11-06 08:15:42

遍历列表Python

2023-11-01 11:17:26

单体架构微服务架构
点赞
收藏

51CTO技术栈公众号