Как проверить, что аннотация метода использует атрибут с определенным значением, используя archunit
У меня есть @Audit
аннотация, он имеет много необязательных атрибутов, мне нужно обеспечить использование одного логического атрибута useAccount = true
для определенных пакетов.
Я пытаюсь использовать archunit для выполнения этой проверки, таким образом, всякий раз, когда разработчик фиксирует код, нарушающий правило, которое нарушает CI, и сообщает команде.
Это сломало бы сборку:
@Audit
public myMethod(...) {
...
}
Это правильный путь:
@Audit(useAccount = true)
public myMethod(...) {
...
}
Проблема в том, что Archunit в настоящее время не поддерживает утверждение по методам. Я ожидал сделать что-то вроде:
methods().that().resideInAnyPackage("..controllers..", "..service..").and().areAnnotatedWith(Audit.class).should(attributeCheckCondition)
Тогда мое пользовательское состояние attributeCheckCondition
позаботится о поиске значения атрибута.
Есть ли способ получения методов при получении классов? Без необходимости писать более сложный предикат и условие?
2 ответа
Поскольку в настоящее время нет базовых определений правил, доступных для методов, необходим промежуточный шаг. ArchUnit имеет ClassesTransformer
превратить JavaClasses в коллекцию других типов.
ClassesTransformer<JavaMethod> methods = new AbstractClassesTransformer<JavaMethod>("methods") {
@Override
public Iterable<JavaMethod> doTransform(JavaClasses javaClasses) {
Set<JavaMethod> allMethods = new HashSet<>();
for (JavaClass javaClass : javaClasses) {
allMethods.addAll(javaClass.getMethods());
}
return allMethods;
}
};
это ClassesTransformer
затем может быть использован в качестве основы для определения пользовательских правил.
ArchRule rule = ArchRuleDefinition.all(methods).that(owner(resideInAnyPackage("..controllers..", "..service.."))).and(annotatedWith(Audit.class)).should(haveAttributeValue());
rule.check(javaClasses);
См. Также Правила с пользовательскими понятиями в Руководстве пользователя и эту проблему.
Я нашел способ сделать это с помощью пользовательского предиката и условия над классами, когда я сделал это, я не знал об ответе Роланда, который кажется лучше, так как он предоставляет способ выразить утверждение правила с точки зрения методов, поэтому я просил.
Однако я хотел опубликовать решение здесь, чтобы оно могло быть полезным для других.
DescribedPredicate<JavaClass> HAVE_A_METHOD_ANNOTATED_WITH_AUDIT =
new DescribedPredicate<JavaClass>("have a method annotated with @Audit")
{
@Override
public boolean apply(JavaClass input)
{
return input.getMethods().stream().anyMatch(method -> method.isAnnotatedWith(Audit.class));
}
};
ArchCondition<JavaClass> ONLY_SET_ATTRIBUTE_USE_ACCOUNT_SET_TO_TRUE =
new ArchCondition<JavaClass>("only set useAccount attribute to true")
{
@Override
public void check(JavaClass item, ConditionEvents events)
{
item.getMethods().stream().filter(method ->
method.isAnnotatedWith(Audit.class) && !method.getAnnotationOfType(Audit.class)
.useAccount()
)
.forEach(method -> {
String message = String.format(
"Method %s is annotated with @Audit but useAccount is not set to true",
method.getFullName());
events.add(SimpleConditionEvent.violated(method, message));
});
}
};
Тогда правило выражается как:
ArchRule ANNOTATION_RULE = classes()
.that()
.resideInAnyPackage("..controller..", "..service..")
.and(HAVE_A_METHOD_ANNOTATED_WITH_AUDIT)
.should(ONLY_SET_ATTRIBUTE_USE_ACCOUNT_SET_TO_TRUE);
Вот еще один собственный пример в дополнение к @raspacorp (который меня вдохновил!).
Проверить
@Secured(ROLE)
аннотации метода, я реализовал следующее правило:
public static class SecuredByRoleArchCondition extends ArchCondition<JavaMethod> {
private final String[] expectedRoles;
public SecuredByRoleArchCondition(String[] expectedRoles) {
super(String.format("accessed by @Secured methods with roles %s", Arrays.toString(expectedRoles)));
this.expectedRoles = expectedRoles;
}
public static SecuredByRoleArchCondition haveSecuredAnnotationWithRoles(String... expectedRoles) {
return new SecuredByRoleArchCondition(expectedRoles);
}
@Override
public void check(JavaMethod javaMethod, ConditionEvents events) {
if (!javaMethod.isAnnotatedWith(Secured.class)) {
String message = String.format("Method %s annotation @Secured(%s) is missing",
javaMethod.getFullName(), Arrays.toString(expectedRoles));
events.add(SimpleConditionEvent.violated(javaMethod, message));
return;
}
String[] annotationRoleValues = javaMethod.getAnnotationOfType(Secured.class).value();
if (!Arrays.equals(annotationRoleValues, expectedRoles)) {
String message = String.format("Method %s @Secured with %s has wrong roles, expected %s instead",
javaMethod.getFullName(), Arrays.toString(annotationRoleValues), Arrays.toString(expectedRoles));
events.add(SimpleConditionEvent.violated(javaMethod, message));
}
}
}
Вот пример использования этого ArchCondition:
@ArchTest
static ArchRule admin_actions_with_post_mapping_should_be_secured_by_ADMIN_WRITE_role =
methods()
.that().areDeclaredInClassesThat().resideInAnyPackage(ADMIN_PACKAGES)
.and().areAnnotatedWith(PostMapping.class)
.should(haveSecuredAnnotationWithRoles("ADMIN_WRITE"));