用这个框架彻底摆脱Controller,从此专注业务——ArcRoute
前言前阵子我写了一篇文章《为什么 Java 里面Service 层不直接返回 Result 对象》没想到那篇文章讨论度很高。很多人赞同也有不少人持反对意见意见“既然 Controller 只是转发参数、包装一下 Result那它存在的意义到底是什么”这个问题问得特别好。因为它刚好触及了很多 Java Web 项目的痛点我们一边强调分层一边又在大量 Controller 里重复写几乎没有业务价值的样板代码。于是我顺着这个问题尝试着往前走了一步做了一个开源框架ArcRoute当然不是要推翻 Spring MVC也从来没想过要否定分层结构只是想试着解决这个问题当 Controller 只剩「声明路由 绑定参数 调用实现 包装响应」这些机械劳动时能不能把这些事情交给框架让业务代码真正回到业务本身先说结论真正反对的是职责错位上一篇文章的核心观点很简单Service 层应该面向业务语义而不是面向 HTTP 响应结构Result 更适合作为接口层、表现层的响应封装业务失败应该优先通过异常和统一机制处理而不是在 Service 里到处 Result.fail(...)业务之间互调时直接传递领域对象比一层层拆 Result 更自然、更可复用这背后反映出一个很实际的工程问题一旦 Service 和 HTTP 响应格式绑死业务层就很容易被表现层污染。可问题来了。如果我们坚持“Service 不直接返回 Result”那很多 Controller 最后就会变成这样lessGetMapping(/user/{id})public ResultUserDTO getUser(PathVariable Long id) {UserDTO dto userService.getUser(id);return Result.success(dto);}很多项目里基本都是这种 Controller这些代码没有任何问题但是看久了会觉得有些无聊。也正是因为这个矛盾我才做了 ArcRoute。我在项目初衷里就明确写了这个框架的起点正是那篇“Service 层是否应该直接返回 Result”的文章以及后续围绕 Controller 样板代码的讨论。(传送门跳转)试图解决的核心问题ArcRoute 的核心思路可以概括成一句话把没有业务承载价值的 Controller 样板统统收敛到框架里。在 ArcRoute 里HTTP 接口不再必须写在 Controller / RestController 里而是可以定义在接口interface上。比如javaApi(basePath /users, produces MediaType.APPLICATION_JSON)public interface UserApi {ApiRoute(path /{id}, method ApiHttpMethod.GET)UserDTO getUser(Path(id) Long id);ApiRoute(path , method ApiHttpMethod.POST, consumes MediaType.APPLICATION_JSON)ResultLong createUser(Body CreateUserCmd cmd);}然后业务实现写在实现类里kotlinApiService(UserApi.class)Servicepublic class UserApiImpl implements UserApi {Overridepublic UserDTO getUser(Long id) {return userService.findById(id);}Overridepublic ResultLong createUser(CreateUserCmd cmd) {return Result.ok(userService.create(cmd));}}框架会在启动时扫描注解自动完成路由注册、参数绑定、校验、调用分发等工作。还是保留了分层思想但不需要再手写胶水代码。把接口定义从实现里拆出来很多人第一次看这种设计会产生误解“这不就是把 Controller 换了个写法吗”表面上看好像是。但本质上ArcRoute 做的是两件事1. 把接口定义和实现解耦传统写法里路由声明、参数注解、实现逻辑都堆在一个类里。ArcRoute 把这两部分拆开了Interface负责描述 API 长什么样Impl负责写业务实现这意味着接口可以更清晰地作为“契约”存在而不是埋在实现细节里。README 也把这一点列为核心特性之一接口声明与实现分离API 定义在 Interface业务在 Impl。2. 把机械流程抽成统一调用链ArcRoute 内置了一条统一调用链参数解析 → 校验 → 前置处理 → 业务调用 → 后置处理 → 响应包装。这意味着很多原本散落在各个 Controller / Advice / Interceptor 里的重复逻辑可以被整合成一条清晰、可插拔的管道。分层还是存在的仍然严格遵守但是能显著减少重复劳动。天然契合我觉得 ArcRoute 最适合宣传的一点不是“能少写 Controller”而是它让业务归业务接口归接口因为在很多项目里之所以 Service 最后开始返回 Result是因为开发者在漫长的代码后开始嫌麻烦后妥协Controller 只是转发还要额外包装一次还要处理异常还要写参数绑定久而久之大家就会想要不干脆 Service 直接把 Result 返回了算了而 ArcRoute 做的事情本质上就是把妥协的诱因拿掉。你不需要为了维持分层额外写一堆机械代码框架已经把路由、绑定、校验、调用分发这些动作做掉了。业务实现就安安心心写业务逻辑。所以我会这么概括它上一篇文章是在回答“为什么不该这么写”ArcRoute 是在回答“那怎样写才不痛苦”。不是只做路由ArcRoute 不只是一个路由扫描器而是围绕接口层做了一套完整的能力编排。目前的核心能力包括动态路由注册、可插拔处理器、局部调用链配置、参数绑定、Bean Validation 支持、统一响应包装以及原生 Servlet/Spring Web 参数注入。我觉得这里面有几个点特别适合拿出来讲。1. 参数绑定更像声明式接口它支持 Path、Query、Header、Body、Part、Cookie、Ctx 等参数绑定方式还支持直接注入 HttpServletRequest、HttpSession、Principal、Locale 这类原生参数。这让接口定义本身更像一个清晰的契约而不是一堆 Controller 方法里的杂糅细节。2. 调用链全程可插拔它支持 ApiPreProcessor、ApiPostProcessor、ApiExceptionProcessor还支持用 ApiPipeline 在接口级或方法级挂载处理器和校验器。这意味着登录态校验审计日志统一异常转换通用埋点特定接口的前后置处理都可以从业务代码里抽出去。3. 统一包装但又允许跳过包装ArcRoute 提供 WrapResult 自动包装返回结果也支持通过 RawResponse 跳过包装。当然还支持用 RawResponse 返回 ResponseEntityResource 和 SseEmitter 这样的原生响应。考虑到不同团队的 Result 结构都不同 ArcRoute 也支持自定义返回的响应体结构。不吹牛的说既保持了灵活性又有统一归口。ArcRoute 至少从设计上给了两个出口正常业务接口走统一包装文件下载、流式响应等特殊场景走原生响应这就比较符合真实项目。低侵入改造我觉得这个框架还有个优点很值得写一写没有另起炉灶。就像 README 里面写的支持 Spring Boot 2.7 / 3.x兼容 Java 8 到 Java 21支持 Javax 和 Jakarta 两套 Servlet 命名空间不修改 Spring 原有机制可与 RestController 共存。所以老项目不必推倒重来。完全可以老接口继续保留原来的 RestController新模块、新子系统、新 API 逐步迁到 ArcRoute先把最烦人的样板区改掉再慢慢演进我想对团队来说渐进式接入才更容易真正落地。它适合什么场景非要说一些适用场景那我推荐几个第一类Controller 大量重复、几乎纯转发的项目这类项目最典型的代码就是typescriptpublic Result? xxx(...) {return Result.success(service.xxx(...));}几十个、几百个接口结构都一样。这种情况下用 ArcRoute 把重复代码提走收益会非常明显。第二类想坚持分层但又讨厌样板代码的团队指那些不想再写几百个一模一样的 Controller 的团队。ArcRoute 本质上就是给这类团队一个工程化解法。第三类需要统一前后置处理能力的项目比如统一鉴权、参数校验、审计、异常转换、接口级扩展这类需求一多传统 Controller 写法很容易散。ArcRoute 的处理器机制和调用链模型会更有组织性。它不适合什么场景强调一下ArcRoute 不是银弹任何技术方案都不是银弹。它不一定适合这些情况1. 项目很小接口也不多如果就十来个接口手写 Controller 完全不是问题没必要为了优雅再加一层抽象。2. 团队对接口驱动写法不熟ArcRoute 的思想不复杂但它毕竟不是大家最熟悉的 Spring MVC 默认写法团队需要一点接受成本。3. 业务接口非常强依赖个性化控制器逻辑如果你的接口层本身就有大量自定义流程Controller 不是空心层那 ArcRoute 的优势会变小。我为什么做这个框架说到底ArcRoute 不是为了整活也不是为了重新发明 Spring MVC。只是想把一件我很在意的事做得再顺畅一点业务代码应该专注业务接口代码应该专注接口重复劳动应该交给框架。上一篇文章里我是在讲一个设计判断Service 层不该直接返回 Result。而这一次我更想把它推进到工程层面如果你真的认同职责分离那就不该只停留在嘴上还应该有一套让开发者愿意坚持这件事的工具。ArcRoute就是我给出的一个答案。它把 “Keeping Services Focused” 直接写进了仓库描述里这个定位其实和上一篇文章是一脉相承的。快速上手如果你想尝试一下可以直接用 Starter 依赖xmldependencygroupIdpub.lighting/groupIdartifactIdarcroute-spring-boot-starter/artifactIdversion0.1.2/version/dependency如果你正在做 Spring Boot 2.7 / 3.x 项目又对“Controller 样板太多、Service 职责容易漂移”这件事感到烦那这个项目也许值得你驻足。GitHub 仓库wuuJiawei/ArcRoute写在最后既然提出了问题那就继续推进尝试解决问题。这是我一贯的工作方式。如果你也认同欢迎去看看 ArcRoute。也欢迎来提 Issue、提建议、提反例。一个项目想要真正成长起来还越来越多人在真实项目里去使用、去反馈、去批评。原文链接https://juejin.cn/post/7616466545535680552