Java 如何使用archunit验证方法注释是否使用具有特定值的属性
我有一个Java 如何使用archunit验证方法注释是否使用具有特定值的属性,java,junit,archunit,Java,Junit,Archunit,我有一个@Audit注释,它有许多可选属性,我需要强制对某些包使用一个布尔属性usecomport=true 我试图使用archunit来完成这个验证,这样每当开发人员提交违反规则的代码时,CI就会打破规则并通知团队 这将破坏构建: @Audit public myMethod(...) { ... } 这是正确的方法: @Audit(useAccount = true) public myMethod(...) { ... } 问题是Archunit目前不支持对方法进行断言。我期待着做一些
@Audit
注释,它有许多可选属性,我需要强制对某些包使用一个布尔属性usecomport=true
我试图使用archunit来完成这个验证,这样每当开发人员提交违反规则的代码时,CI就会打破规则并通知团队
这将破坏构建:
@Audit
public myMethod(...) {
...
}
这是正确的方法:
@Audit(useAccount = true)
public myMethod(...) {
...
}
问题是Archunit目前不支持对方法进行断言。我期待着做一些类似的事情:
methods().that().resideInAnyPackage("..controllers..", "..service..").and().areAnnotatedWith(Audit.class).should(attributeCheckCondition)
然后,我的自定义条件attributeCheckCondition
将负责查看属性值
当我们检索类时,有没有一种方法可以检索方法?无需编写更复杂的谓词和条件?Update
自ArchUnit 0.10.0以来,可以为成员创建规则
methods().that().areDeclaredInClassesThat().resideInAnyPackage("..controllers..", "..service..").and().areAnnotatedWith(Audit.class).should(attributeCheckCondition)
另请参见《用户手册》中的
原始答案
由于目前没有可用于方法的基本规则定义,因此需要中间步骤。ArchUnit有一个ClassesTransformer
,用于将Java类转换为其他类型的集合
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;
}
};
另请参见《用户指南》和。我找到了一种使用自定义谓词和类上的条件的方法,当我这样做时,我不知道Roland的响应似乎更好,因为它提供了一种从方法角度表达规则断言的方法,这就是我要求的原因 不过,我想在这里发布解决方案,以便对其他人有用
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));
});
}
};
这里是除了@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));
}
}
}
有趣的事实:在你提出问题的前一天,我遇到了一个类似的问题,我用与你相同的方法解决了它D
ArchRule ANNOTATION_RULE = classes()
.that()
.resideInAnyPackage("..controller..", "..service..")
.and(HAVE_A_METHOD_ANNOTATED_WITH_AUDIT)
.should(ONLY_SET_ATTRIBUTE_USE_ACCOUNT_SET_TO_TRUE);
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));
}
}
}
@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"));