官方提供的自定义规则的例子如下:
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; } }; 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)); } } } }; classes().that(haveAFieldAnnotatedWithPayload).should(onlyBeAccessedBySecuredMethods);我们只需要模仿它的实现即可,具体如下:
public class ArchunitTest { @Test public void controller_class_rule() { JavaClasses classes = new ClassFileImporter().importPackages("club.throwable"); DescribedPredicate<JavaClass> predicate = new DescribedPredicate<JavaClass>("定义在club.throwable.controller包下的所有类") { @Override public boolean apply(JavaClass input) { return null != input.getPackageName() && input.getPackageName().contains("club.throwable.controller"); } }; ArchCondition<JavaClass> condition1 = new ArchCondition<JavaClass>("类名称以Controller结尾") { @Override public void check(JavaClass javaClass, ConditionEvents conditionEvents) { String name = javaClass.getName(); if (!name.endsWith("Controller")) { conditionEvents.add(SimpleConditionEvent.violated(javaClass, String.format("当前控制器类[%s]命名不以\"Controller\"结尾", name))); } } }; ArchCondition<JavaClass> condition2 = new ArchCondition<JavaClass>("方法的入参类型命名以\"Request\"结尾,返回参数命名以\"Response\"结尾") { @Override public void check(JavaClass javaClass, ConditionEvents conditionEvents) { Set<JavaMethod> javaMethods = javaClass.getMethods(); String className = javaClass.getName(); // 其实这里要做严谨一点需要考虑是否使用了泛型参数,这里暂时简化了 for (JavaMethod javaMethod : javaMethods) { Method method = javaMethod.reflect(); Class<?>[] parameterTypes = method.getParameterTypes(); for (Class parameterType : parameterTypes) { if (!parameterType.getName().endsWith("Request")) { conditionEvents.add(SimpleConditionEvent.violated(method, String.format("当前控制器类[%s]的[%s]方法入参不以\"Request\"结尾", className, method.getName()))); } } Class<?> returnType = method.getReturnType(); if (!returnType.getName().endsWith("Response")) { conditionEvents.add(SimpleConditionEvent.violated(method, String.format("当前控制器类[%s]的[%s]方法返回参数不以\"Response\"结尾", className, method.getName()))); } } } }; ArchRuleDefinition.classes() .that(predicate) .should(condition1) .andShould(condition2) .because("定义在controller包下的Controller类的类名称以\"Controller\"结尾,方法的入参类型命名以\"Request\"结尾,返回参数命名以\"Response\"结尾") .check(classes); } }因为导入了所有需要的编译好的类的静态属性,基本上是可以编写所有能够想出来的规约,更多的内容或者实现可以自行摸索。
小结通过最近的一个项目引入了Archunit,并且进行了一些编码规范和架构规范的规约,起到了十分明显的效果。之前口头或者书面文档的规范可以通过单元测试直接控制,项目构建的时候强制必须执行单元测试,只有所有单测通过才能构建和打包(禁止使用-Dmaven.test.skip=true参数),起到了十分明显的成效。
参考资料:
User Guide
个人博客Throwable's Blog