如何用ArchUnit验证Controller方法仅调用一次Service层方法?
用ArchUnit验证Controller方法仅调用一次Service方法
ArchUnit可不是只能检查类和方法的元数据——它底层基于ASM字节码解析,完全能深入方法内部统计调用次数,刚好能解决你这个问题。下面是具体的实现方案:
1. 先明确校验范围
首先得把Controller和Service的范围定清楚,比如用注解或者包路径来匹配:
// 匹配所有Controller类 Classes<JavaClass> controllers = classes().that() .areAnnotatedWith(RestController.class) .or().areAnnotatedWith(Controller.class); // 定义Service类的匹配规则 DescribedPredicate<JavaClass> isService = DescribedPredicate.describe("是Service类", javaClass -> javaClass.isAnnotatedWith(Service.class));
2. 编写调用次数校验规则
通过ArchUnit的ArchCondition深入方法内部,统计每个Controller方法调用Service方法的次数:
ArchRule controllerSingleServiceCallRule = ArchRuleDefinition.methods() .that().areDeclaredIn(controllers) .and().arePublic() // 一般Controller对外方法都是public,可按需调整 .should(new ArchCondition<JavaMethod>("最多调用一次Service方法") { @Override public void check(JavaMethod method, ConditionEvents events) { // 找出当前方法里所有调用Service类的方法 List<MethodCall> serviceMethodCalls = method.getMethodCallsFromSelf() .stream() .filter(call -> isService.test(call.getTargetOwner())) .collect(Collectors.toList()); // 如果调用次数超过1次,就标记为违规 if (serviceMethodCalls.size() > 1) { events.add(SimpleConditionEvent.violated(method, String.format("方法 %s 调用了 %d 次Service方法:%s", method.getFullName(), serviceMethodCalls.size(), serviceMethodCalls.stream().map(MethodCall::getFullName).collect(Collectors.joining(", ")) ) )); } } });
3. 处理例外情况(可选)
如果有些Controller方法确实需要调用多个Service(比如经过评审的合法场景),可以自定义一个注解来排除这些方法:
// 自定义注解,标记允许多Service调用的方法 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AllowMultipleServiceCalls {} // 修改规则,排除带有该注解的方法 ArchRule controllerSingleServiceCallRule = ArchRuleDefinition.methods() .that().areDeclaredIn(controllers) .and().arePublic() .and().areNotAnnotatedWith(AllowMultipleServiceCalls.class) .should(...);
几点注意事项
- 这个方案能覆盖所有编译后的方法调用,不管是直接调用Service实现类,还是通过接口调用的实现类,都能被识别。
- 不管你是用
@Autowired字段注入,还是构造注入、工厂方法获取Service实例,只要调用的是Service类的方法,都会被统计到。 - 大型项目全量检查时,方法内部分析会增加一点耗时,你可以通过限定检查的包路径(比如
classes().that().resideInAPackage("com.yourcompany.controller"))来提升性能。
内容的提问来源于stack exchange,提问作者Steven
相关产品推荐
相关产品推荐

