ArchUnit 中的 AnyOf 谓词

AnyOf-Predicate in ArchUnit

我想测试所有 Hibernate 关联注释(@ManyToOne、@OneToMany、@OneToOne、@ManyToMany)是否都在使用 fetch = FetchType.LAZY。这是有效的:

private static final Set<Class<? extends Annotation>> associations =
         Set.of(ManyToOne.class, OneToMany.class, OneToOne.class, ManyToMany.class);

@ArchTest
public static final ArchRule allEntityRelationsShouldBeLazy = fields().that()
      .areDeclaredInClassesThat().areAnnotatedWith(Entity.class)
      .and()
      .areAnnotatedWith(ManyToOne.class)
      .or().areAnnotatedWith(OneToMany.class)
      .or().areAnnotatedWith(OneToOne.class)
      .or().areAnnotatedWith(ManyToMany.class)
      // what I'd like: .areAnnotatedWith(Any.of(associations))
      .should()
      .notBeAnnotatedWith(new DescribedPredicate<>("FetchType.EAGER or undefined FetchType") {
         @Override
         public boolean apply(JavaAnnotation<?> input) {
            JavaClass rawType = input.getRawType();
            if (!rawType.isEquivalentTo(OneToOne.class)
            // what I'd like: if (!Any.of(associations).apply(input)) {
                  && !rawType.isEquivalentTo(OneToMany.class)
                  && !rawType.isEquivalentTo(ManyToOne.class)
                  && !rawType.isEquivalentTo(ManyToMany.class)) {
               // Filter again, because a field can contain multiple annotations.
               return false;
            }
            return input.get("fetch")
                  .transform(JavaEnumConstant.class::cast)
                  .transform(fetchType -> !FetchType.LAZY.name().equals(fetchType.name()))
                  .or(true);
         }
      });

我必须过滤两次,因为一个字段可以有多个注释,如下所示:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "otherEntity", referencedColumnName = "id")
private OtherEntity otherEntity;

我不喜欢的是我必须写两次注解(ManyToOne…)。为什么 ArchUnit 中没有“anyOf”谓词?或者有什么其他方法可以避免重复它们吗?

你想要的可以用Java流操作构建:

import static com.tngtech.archunit.base.DescribedPredicate.describe;
import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields;


private static final DescribedPredicate<CanBeAnnotated> ALWAYS_FALSE = new DescribedPredicate<CanBeAnnotated>("always false", new Object[0]) {
        @Override
        public boolean apply(CanBeAnnotated input) {
            return false;
        }
    };
@ArchTest
ArchRule allEntityRelationsShouldBeLazy = fields().that()
    .areDeclaredInClassesThat().areAnnotatedWith(Entity.class)
    .and(are(annotatedWithAnyOf(associations)))
    .should().notBeAnnotatedWith(describe("FetchType.EAGER or undefined FetchType",
        input -> associations.stream().anyMatch(input.getRawType()::isEquivalentTo)
                 && input.get("fetch")
                     .transform(JavaEnumConstant.class::cast)
                     .transform(enumConst -> !FetchType.LAZY.name().equals(enumConst.name()))
                     .get()  // associations have a default fetch type
    ));
    
DescribedPredicate<CanBeAnnotated> annotatedWithAnyOf(Collection<Class<? extends Annotation>> annotations) {
    return annotations.stream()
            .map(CanBeAnnotated.Predicates::annotatedWith)
            .reduce(DescribedPredicate::or)
            .orElse(ALWAYS_FALSE);  // annotations must not be empty
}

实体字段使用自定义条件,直接操作associations的相关注解即可,完全不用重复:

@ArchTest
ArchRule allEntityRelationsShouldBeLazy = fields()
    .that().areDeclaredInClassesThat().areAnnotatedWith(Entity.class)
    .should(new ArchCondition<JavaField>("be annotated with FetchType.LAZY if an association") {
        @Override
        public void check(JavaField field, ConditionEvents events) {
            field.getAnnotations().stream()
                .filter(annotation -> associations.stream().anyMatch(annotation.getRawType()::isEquivalentTo))
                .forEach(association -> {
                    JavaEnumConstant fetchType = (JavaEnumConstant) association.get("fetch").get();
                    if (!FetchType.LAZY.name().equals(fetchType.name())) {
                        String message = field.getDescription() + " is not LAZY in " + field.getSourceCodeLocation();
                        events.add(SimpleConditionEvent.violated(field, message));
                    }
                });
        }
    });