简体   繁体   English

如何使用 archunit 验证方法注释是否使用具有特定值的属性

[英]How to validate that a method annotation is using an attribute with an specific value using archunit

I have an @Audit annotation, it has many optional attributes, I need to enforce the use of one boolean attribute useAccount = true for certain packages.我有一个@Audit注释,它有许多可选属性,我需要强制对某些包使用一个布尔属性useAccount = true

I am trying to use archunit to accomplish this validation, that way whenever a developer commits code that breaks the rule the CI will break and inform the team.我正在尝试使用 archunit 来完成此验证,这样每当开发人员提交违反规则的代码时,CI 就会破坏并通知团队。

This would break the build:这将破坏构建:

@Audit
public myMethod(...) {
...
}

This is the right way:这是正确的方法:

@Audit(useAccount = true)
public myMethod(...) {
...
}

The problem is that Archunit doesn't currently support asserting over methods.问题是 Archunit 目前不支持对方法进行断言。 I was expecting to do something like:我期待做这样的事情:

methods().that().resideInAnyPackage("..controllers..", "..service..").and().areAnnotatedWith(Audit.class).should(attributeCheckCondition)

Then my custom condition attributeCheckCondition would take care of looking into the attribute value.然后我的自定义条件attributeCheckCondition将负责查看属性值。

Is there a way of retrieving methods as we retrieve classes?有没有办法在我们检索类时检索方法? Without having to write a more complicated predicate and condition?无需编写更复杂的谓词和条件?

Update更新

Since ArchUnit 0.10.0 it is possible to create rules for members.从 ArchUnit 0.10.0 开始,可以为成员创建规则。

methods().that().areDeclaredInClassesThat().resideInAnyPackage("..controllers..", "..service..").and().areAnnotatedWith(Audit.class).should(attributeCheckCondition)

See also Composing Member Rules in the User Guide.另请参阅用户指南中的编写成员规则

Original Answer原答案

Since there are currently no basic rule definitions available for methods, an intermediate step is necessary.由于目前没有可用于方法的基本规则定义,因此需要一个中间步骤。 ArchUnit has a ClassesTransformer to transform JavaClasses into a collection of other types. 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;
    }
};

This ClassesTransformer can then be used as a base for custom rule definitions.然后可以将此ClassesTransformer用作自定义规则定义的基础。

ArchRule rule = ArchRuleDefinition.all(methods).that(owner(resideInAnyPackage("..controllers..", "..service.."))).and(annotatedWith(Audit.class)).should(haveAttributeValue());
rule.check(javaClasses);

See also Rules with Custom Concepts in the User Guide and this issue .另请参阅用户指南中的带有自定义概念的规则本期

I found a way of doing it with custom predicate and condition over classes, when I did that I was not aware of Roland's response which seems to be better, as it provides a way to express the rule assertion from the methods perspective which is why I was asking for.我找到了一种通过类的自定义谓词和条件来做到这一点的方法,当我这样做时,我不知道 Roland 的响应似乎更好,因为它提供了一种从方法角度表达规则断言的方法,这就是为什么我要求。

However I wanted to post the solution here so it can be useful for others.但是我想在这里发布解决方案,以便对其他人有用。

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));
                });
        }
    };

Then the rule is expressed as:那么规则表示为:

ArchRule ANNOTATION_RULE = classes()
    .that()
    .resideInAnyPackage("..controller..", "..service..")
    .and(HAVE_A_METHOD_ANNOTATED_WITH_AUDIT)
    .should(ONLY_SET_ATTRIBUTE_USE_ACCOUNT_SET_TO_TRUE);

Here is another custom example in addition to @raspacorp (who inspired me!).这是除@raspacorp(谁启发了我!)之外的另一个自定义示例。

To check @Secured(ROLE) method annotation, I've implemented the following rule:为了检查@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));
        }
    }
}

Here is a sample usage of this archCondition:以下是此 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"));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM