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));
}
});
}
});
我想测试所有 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));
}
});
}
});