1. 接口文档这事为什么成了Spring Boot项目的隐形痛点国内做Java后端的朋友十有八九都经历过这种场景接口写完了代码push上去然后前端同事跑来问这个订单列表接口的参数是什么来着你翻半天代码找到Controller念给对方听对方记不住你也嫌烦。更痛苦的是项目迭代三个月后接口文档早就不跟代码同步了——改了个字段名文档还是旧版前端联调时按文档传参数接口直接报错。这不是管理问题是工具链的问题。只要文档靠人工维护就一定会腐烂。Spring Boot项目里常见的解决方案有两种Swagger和JApiDocs。两个工具我都深度用过也帮团队做过选型今天把这两种方案从头到尾捋一遍。这篇文章不是给你念官方文档而是站在实际落地的角度告诉你它们各自适合什么场景、怎么集成、有哪些坑是文档里不会写的。先给结论Swagger生态成熟但笨重JApiDocs轻量但功能有限。具体怎么选取决于你的项目阶段、团队协作方式和接口数量。下面细说。2. 两个工具的核心机制差异运行时扫描与静态解析2.1 SwaggerSpringDoc是怎么工作的市面上说的Spring Boot集成Swagger现在主流方案其实是springdoc-openapi这个库它底层用的是Swagger 2/OpenAPI 3规范。老一点的springfox已经基本不维护了新项目别再踩这个坑。springdoc的工作方式可以简单概括为应用启动时通过Spring容器扫描所有标注了RestController和Api注解的接口通过反射机制读取Controller方法的注解、参数类型、返回值类型然后动态生成OpenAPI JSON文档。这里有个关键点——它是运行时扫描、运行时生成。这就意味着你的应用必须真正跑起来Swagger才能工作。项目没启动文档就不存在。而且因为是基于反射它对泛型、继承这种复杂结构有时候会解析得不够准确需要额外写注解来兜底。用springdoc生成出来的Swagger UI页面是一套独立的前端资源。你访问/swagger-ui.html或/swagger-ui/index.html页面会向后端发请求获取OpenAPI JSON默认路径/v3/api-docs再渲染成树状的接口列表左侧按Controller分组点开能看到每个接口的请求方法、参数说明、响应结构。还能直接在上面发起请求测试相当于内嵌了一个轻量级Postman。2.2 JApiDocs和Swagger是两条思路JApiDocs的做法完全不一样。它不扫描运行时容器而是直接解析源代码文件。你写一个带标准Javadoc注释的ControllerJApiDocs通过解析Java源码的抽象语法树AST提取出类名、方法名、参数名、返回值类型、字段含义这些信息然后直接生成Markdown、HTML或离线文档。这意味着两件事第一JApiDocs不需要启动Spring应用就能生成文档编译级别就能完成第二它对源码注释的规范性要求非常高——如果你的代码没写Javadoc或者注释写得马马虎虎生成的文档就是一堆光秃秃的方法名没法看。为了更直观看清楚两者的差异下面这个表是我整理的对比维度SwaggerspringdocJApiDocs文档生成时机应用运行时动态生成编译/静态解析源码生成是否需要启动应用需要不需要主要规范基础OpenAPI 3注解驱动Javadoc注释驱动生成文档格式JSON可转OpenAPI规范Markdown / HTML / 自定义模板调试接口能力自带UI可直接请求依赖生成的文档配合Postman学习成本注解较多有学习曲线只要会写注释就能用对代码侵入性高每个接口都要加注解低规范注释即可泛型解析能力反射解析复杂泛型容易出偏差强类型解析更贴合源码语义未授权访问风险高需手动配置拦截低不暴露动态页面2.3 为什么说Swagger未授权访问漏洞几乎成了标配风险从上面的表你应该能看出一个安全隐患Swagger必须在运行时暴露一个可访问的URL来提供文档和调试UI。很多团队图省事上线时啥也不改直接带着/swagger-ui和/v3/api-docs上生产。结果就是任何人都能打开这个页面看到你项目里所有的接口定义、请求参数、响应结构。更有甚者直接在UI里发起请求等于把你的后端管理接口当成了公共API来调。我做过一次渗透测试的检视一个中型电商项目的Swagger页面泄露出来的接口有140多个其中包含了内部订单查询、用户信息导出这类敏感接口而且没有任何鉴权。这种问题一旦被扫描器抓到就是高危漏洞。你去看安全扫描报告里常见的Swagger API未授权访问漏洞【原理扫描】【可验证】说的就是这件事。很多人的理解是Swagger只需要在开发环境开启就行。但是在Spring Boot里如果你用的是springdoc-openapi它默认是开启的没有根据环境自动关闭的机制。你想要实现dev环境开启、prod环境关闭必须自己写配置。这一点后面我会给出一套完整的生产环境关闭方案特别重要。3. Spring Boot集成Swagger的实操流程与关键参数3.1 依赖选型和版本搭配先说版本这个最容易踩坑的地方。springdoc-openapi有两条版本线1.x适配Spring Boot 2.x2.x适配Spring Boot 3.x因为Spring Boot 3基于Jakarta命名空间javax改成了jakarta。你如果脑子一热把springdoc 2.6.0塞进Spring Boot 2.7项目里启动的时候会报ClassNotFoundException大概率是javax.servlet找不到。具体引入方式Maven项目在pom.xml里加!-- Spring Boot 2.x 用这个 -- dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-ui/artifactId version1.7.0/version /dependency !-- Spring Boot 3.x 用这个 -- dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version2.3.0/version /dependency如果你用的是WebFlux而非WebMVC对应的依赖是springdoc-openapi-starter-webflux-ui。这个别选错否则接口列表完全扫不出来。3.2 一个不能再基础的Swagger配置类引入依赖后其实你什么都不配访问/v3/api-docs就已经能拿到一坨JSON了。但那个JSON的info字段是空白的文档标题、版本号、接口前缀信息都没有。要让它变得可用至少得有一个配置类package com.example.config; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class OpenApiConfig { Bean public OpenAPI customOpenAPI() { return new OpenAPI() .info(new Info() .title(订单中心服务API文档) .version(2.0.0) .description(订单中心服务对外提供的RESTful接口文档供前端联调与第三方系统对接使用。) .contact(new Contact() .name(后端架构组) .email(backendexample.com))); } }3.3 接口层面的核心注解Tag和Operation在springdoc体系里最常用的两个注解是Tag和Operation。Tag放在Controller类上相当于分组名替代老springfox时代的ApiOperation放在方法上替代ApiOperation。package com.example.controller; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api/order) Tag(name 订单接口, description 订单的创建、查询、取消等操作) public class OrderController { GetMapping(/detail/{orderId}) Operation(summary 查询订单详情, description 根据订单ID查询订单的详细信息和商品明细列表) public ApiResultOrderDetailVO getOrderDetail(PathVariable(orderId) Long orderId) { return null; } PostMapping(/create) Operation(summary 创建订单, description 用户下单时调用创建一张待支付订单) public ApiResultLong createOrder(RequestBody CreateOrderRequest request) { return null; } }这里有一个我反复跟团队强调的点Swagger UI展示的中文可读性完全取决于你的注解写得有多认真。如果你们全都是OrderController光秃秃的GetMapping(/detail/{orderId})没有summary没有description前端拿到文档后看到的就是一堆英文方法名和参数名等于没生成。很多人说Swagger文档不好用其实不是工具的问题是你们的注解偷懒了。3.4 参数模型会不会显示取决于VO的规范Swagger能自动解析Java对象的字段作为请求/响应示例但它解析的依据是类型的源码结构。看下面这个请求VOpackage com.example.dto; import io.swagger.v3.oas.annotations.media.Schema; public class CreateOrderRequest { Schema(description 商品ID, example 10086) private Long productId; Schema(description 购买数量, example 2) private Integer quantity; Schema(description 收货地址ID, example 5001) private Long addressId; // getter/setter 省略 }Schema注解能指定每个字段的中文含义和示例值。这个字段层面的描述对前端联调帮助巨大能让对方直接看到productId是什么、传什么格式。这里分享一个排查经验如果你发现Swagger UI上的接口模型里字段和实际代码不一致多数情况是应用没重新编译、IDE缓存了旧Class。Swagger是反射加载它读的是JVM中已经加载的类不是你的源代码所以改了代码必须重启应用才能看到变化。你热部署了但没完全重启的话它可能会显示旧的字段结构这是一个非常容易引起困惑的细节。3.5 生产环境如何安全地关闭Swagger下面给出一个比较稳妥的生产环境关闭方案按下面几步做能最大程度避免Swagger未授权访问问题。首先添加一个开关配置在application.yml里springdoc: api-docs: enabled: true # 可以通过环境变量覆盖 swagger-ui: enabled: true然后在application-prod.yml里关掉springdoc: api-docs: enabled: false swagger-ui: enabled: false如果你们有Spring Cloud Config或Nacos配置中心可以把springdoc.api-docs.enabled这个配置项放到配置中心统一管理这样不用改代码、重启应用直接在配置中心动态调整开关。我实际项目中就这么做的出现紧急安全扫描时生产环境想紧急关闭文档改配置中心一个键值30秒生效。另外提醒一句只设置springdoc.swagger-ui.enabledfalse还不够/v3/api-docs这个JSON端点还是能访问必须两边都关掉或者用Spring Security对这两个路径做拦截。4. JApiDocs的集成方式与注释规范一套注释撬动整个文档4.1 引入与配置JApiDocs的使用路径非常轻量核心步骤就是引入依赖、写一段生成代码、运行main方法。在Maven里加dependency groupIdio.github.yedaxia/groupId artifactIdjapidocs/artifactId version1.4.4/version /dependency然后在任意一个测试类或独立的main方法里执行package com.example; import io.github.yedaxia.apidocs.Docs; import io.github.yedaxia.apidocs.DocsConfig; public class ApiDocGenerator { public static void main(String[] args) { DocsConfig config new DocsConfig(); config.setProjectPath(D:/workspace/my-springboot-project); // 项目根路径 config.setProjectName(订单中心服务); config.setApiVersion(2.0.0); config.setDocsPath(D:/docs/api); // 生成文档的输出目录 config.setAutoGenerate(Boolean.TRUE); Docs.buildHtmlDocs(config); } }不夸张地说JApiDocs整个集成过程就这十几行代码。它自己会去扫描projectPath下的所有Controller源码不需要Spring容器、不需要启动应用直接输出HTML。4.2 Javadoc注释是它的命脉JApiDocs读取的核心是Javadoc所以Controller注释的写法有严格要求/** * 订单接口 */ RestController RequestMapping(/api/order) public class OrderController { /** * 查询订单详情 * * param orderId 订单ID * return 订单详情对象 */ GetMapping(/detail/{orderId}) public ApiResultOrderDetailVO getOrderDetail(PathVariable(orderId) Long orderId) { return null; } }注意两点第一param标签后面必须跟真实的参数名不能是IDE自动生成的arg0、arg1否则文档参数名完全不可读。第二返回类型如果是ApiResultT这种统一包装结构JApiDocs能解析出里面的data字段结构前提是泛型写完整。像ApiResultOrderDetailVO这样写文档里才会显示OrderDetailVO的字段写ApiResult这种裸类型它只能解析出code、message这种顶层字段data就是空的。4.3 JApiDocs和Swagger的字段级注释差异Swagger是注解驱动字段描述靠Schema。JApiDocs是注释驱动字段描述靠实体类的Javadoc注释public class OrderDetailVO { /** * 订单编号 */ private String orderNo; /** * 订单金额单位分 */ private Long totalFee; /** * 订单状态0-待支付1-已支付2-已取消3-已退款 */ private Integer status; // getter/setter }看到了吗只要每行加一行/** 描述 */注释JApiDocs就能把这些字段搬进文档里。从敏捷交付角度来说这比Swagger到处标Schema要舒服很多因为Java开发本来就该写注释写都写了顺带生成文档零额外成本。4.4 一套Controller能不能同时兼容JApiDocs和Swagger这个问题很多人问过答案是可以的。做法是Controller方法上只写Javadoc不写Operation但为了Swagger UI的友好显示加上Tag和Operation的summary属性。JApiDocs对Operation注解是无感的它只认Javadoc和param标签两套互不干扰。这样你既能在开发阶段用Swagger做接口调试和联调又能在需要离线文档时执行JApiDocs生成一套干净的HTML发给前端。不过我的实际体验是——同时维护两套工具会让Controller代码变得很啰嗦各种注解、注释堆在一起可读性反而下降。所以一般还是建议二选一做主力另一套按需用脚本生成。5. 生产环境配置经验Swagger安全关闭、多环境联动与接口分组5.1 不止是关掉UI还要防JSON端点被扫到我在团队的安全评审里反复强调一个观点很多人配置Swagger时只知道配置UI访问路径却忽略了底层JSON接口。Springdoc的/v3/api-docs端点返回的是结构化的JSON包含所有接口信息攻击者拿到了这个JSON跟拿到Swagger UI页面没有任何本质区别甚至更方便——直接按JSON写脚本批量调用。有些自动扫描工具就是专门盯这两个端点的。所以关闭方案必须同时处理两个端点。用Spring Security的话可以这样写package com.example.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain; Configuration public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth - auth .requestMatchers(/swagger-ui/**, /v3/api-docs/**).hasRole(SWAGGER_VIEW) .anyRequest().permitAll() ); return http.build(); } }这只是个思路具体怎么配取决于你们的权限模型。如果项目里没有Spring Security那就老老实实用配置中心动态开关或者用过滤器拦截这两个路径。5.2 多环境场景下如何分层管理举个实际场景开发环境所有人要能看Swagger方便前端同学自测测试环境的接口文档要带上测试数据方便QA写用例生产环境必须关闭。这种情况下我推荐做法是用spring.config.activate.on-profile结合环境变量把开关配置抽离成环境专属配置# application-dev.yml springdoc: api-docs: enabled: true swagger-ui: enabled: true tags-sorter: alpha operations-sorter: method# application-test.yml springdoc: api-docs: enabled: true swagger-ui: enabled: true# application-prod.yml springdoc: api-docs: enabled: false swagger-ui: enabled: falsetags-sorter和operations-sorter这两个配置值得提一下。它们控制文档页面的接口排序方式tags-sorter: alpha按Controller名称字母排序operations-sorter: method按HTTP方法排序。如果不设默认按接口在Controller中的声明顺序展示接口多的时候找起来很费劲。5.3 除了关闭还能怎么给Swagger加访问控制有时候内部测试阶段需要保留生产环境的Swagger供联调用但又不希望任何人匿名访问。这时候解决办法是给Swagger页面套一层基础登录认证。Spring Security里加一个简单的httpBasic认证只保护Swagger相关路径http.authorizeHttpRequests(auth - auth .requestMatchers(/swagger-ui/**, /v3/api-docs/**).authenticated() .anyRequest().permitAll() ) .httpBasic(withDefaults());密码用环境变量注入别写死在配置文件里。虽然这种保护强度不算高但对内网环境来说已经足够挡住绝大多数误入者了。5.4 接口分组一个服务拆多组文档项目一大各种业务模块全都在一个Spring Boot服务里Swagger UI左侧列表会非常长。可以用springdoc.group-configs按包路径拆分springdoc: group-configs: - group: order packages-to-scan: com.example.controller.order - group: user packages-to-scan: com.example.controller.user - group: payment packages-to-scan: com.example.controller.payment配置后启动服务访问/v3/api-docs/order、/v3/api-docs/user、/v3/api-docs/payment会分别拿到不同模块的OpenAPI JSONSwagger UI左上角会出现下拉切换。这个功能在多团队协作同一个代码仓库时特别有用各团队只看自己关心的模块眼不见心不烦。6. 选型建议结合项目阶段和团队习惯的判断框架6.1 选型对比表按场景匹配工具我在多个项目里做过选型评估这里直接给一个比较宽泛的决策参考项目特征推荐方案核心原因团队协作紧密前后端联调频繁需要在线调试SwaggerUI自带发起请求功能前端可以直接在页面里自查接口接口数量多50且高度依赖源代码可读性JApiDocs源码注释即文档不引入额外注解代码更干净项目对界面要求高希望文档能交付给外部客户JApiDocs生成的离线HTML可以进一步定制样式已有API网关如Spring Cloud Gateway接口全部走网关Swagger网关聚合Swagger能通过网关聚合多个服务的文档管理方便项目初期快速搭建接口很少只需要一个简单文档JApiDocs学习成本极低5分钟就能跑通有严格的接口版本管理要求需要遵循OpenAPI规范Swagger生成的JSON本身是OpenAPI规范能接入API生命周期管理工具代码注释规范已经很好不想再为文档写任何额外注解JApiDocs注释即文档零额外成本6.2 决策前的三个核心问题如果你还在纠结我建议你现在先回答三个问题第一你们的接口消费者是谁如果是外部合作伙伴、第三方开发者往往需要一份清晰、可打印、可交付的离线文档那JApiDocs的HTML文档比Swagger Web页面更容易传递。如果是自己公司的前端、测试他们习惯直接在网页上点来点去调试那Swagger的UI优势非常明显。第二代码注释规范能做到什么程度JApiDocs依赖Javadoc注释质量如果你们的Controller注释常年是空的光写注释这一件事就会引发一轮管理成本。反过来Swagger即使没注释也能生成粗糙的文档结构但不好用而已。第三接口数量规模和模块复杂度几十个接口的项目用什么都行。但两三百个接口、多个业务模块聚合在一个服务时Swagger的分组配置让每个团队各看各的体验会好很多JApiDocs扫出来的单个HTML文件会非常庞大模块定位反而困难。6.3 一个折中的新趋势从代码直接生成Markdown接口文档除了上面两种方案现在还有一种思路是用springdoc-openapi导出OpenAPI JSON或YAML再配合工具转成Markdown。比如用Swagger的json文件配合widdershins之类的工具可以生成一份带示例的Markdown文档方便沉淀到Git或Confluence。实际操作时可以用一个启动参数或一个定时任务在CI构建阶段生成JSON然后脚本转换入库。这个方案的好处是既能用Swagger自动化的能力又能把文档从运行时页面变成静态文件资产进行版本管理。不过缺点是链路较复杂需要额外的CI配置和脚本维护适合团队已经具备一定DevOps基础的情况不太适合小团队。6.4 别忽略的隐形成本注解维护和代码可读性最后聊一个容易被忽略的点——工具带来的隐形成本。Swagger的注解用多了Controller代码会被各种Operation、ApiResponse、Schema填满核心业务逻辑反而被淹没了。我见过一个项目的Controller方法体只有三行但方法上面的注解写了整整四十行。JApiDocs虽然让你少写注解但它对你的注释质量提出了更高要求。如果你的团队本来就没形成写清楚方法和字段注释的文化那JApiDocs生成的文档质量会非常难看最终还是会回到手动补文档的老路。所以在选型之前先审视一下团队代码规范的实际执行情况。工具是放大器注释规范好JApiDocs能把规范变成文档红利代码规范差Swagger至少还能靠注解硬撑出勉强能看的页面。7. 常见落地问题排查我从实际项目中捞出来的经验7.1 Swagger UI能打开但接口列表是空的这个问题我在新项目里遇到过不止一次。最典型的原因是springdoc扫描的包路径和你Controller所在的包路径不一致。如果你的Application类在com.example.appController在com.example.web.controllerspringdoc默认会扫描Application类所在包的子树。解决办法是在配置类上加ComponentScan或在配置里指定packages-to-scanspringdoc: packages-to-scan: com.example.web.controller, com.example.api.controller另一种可能是Controller没有加RestController或Controller加ResponseBodyspringdoc只认Spring MVC的处理器映射。这个排查方法很直接先去访问/v3/api-docs看JSON里paths节点是不是空的。如果JSON是空的那就是后端没扫到如果JSON有数据但UI空白那是静态资源或浏览器缓存问题换个无痕窗口试试。7.2 JApiDocs生成的Markdown表格里字段又是乱的用JApiDocs输出Markdown时如果你的实体类里有继承关系比如OrderVO extends BaseVOJApiDocs对父类字段的解析可能不够全面生成的表格里会缺少父类字段。我遇到之后仔细看过它的源码它在处理继承这块确实不如Swagger的反射机制完善。解决办法有两条路一是把公共字段复制到子类里牺牲一点代码洁癖换文档完整度二是在实体类上显式声明字段或者在Javadoc里补充说明。如果你经常要生成文档给外部看建议尽量保持实体类的扁平化设计减少多层继承。7.3 返回类型是ResponseEntity或统一Result时JApiDocs不显示泛型内容这个问题有点迷惑性。假设接口返回类型是ResponseEntityApiResultOrderDetailVOJApiDocs解析起来容易出问题。它会认为返回的是ResponseEntity而不是里面的OrderDetailVO。我在文档生成后经常发现data字段下没有任何详情只有顶层字段。一个比较有效的规避方式是让接口方法返回业务对象而不是ResponseEntity让Spring MVC自动处理状态码包装。如果你们的架构统一使用ApiResultT返回类型直接写ApiResultOrderDetailVOJApiDocs和Swagger都能正确解析泛型。这不是让你改全局返回结构只是在写Controller方法时尽量让泛型保持完整。7.4 Swagger UI加载慢接口多的时候白屏如果项目接口数量特别多Swagger UI首次加载会发送一次请求拿全部JSON数据量大时渲染页面会明显卡顿。我遇到过白屏的情况多数是浏览器限制了请求体大小或者Nginx缓存导致JSON不完整。简单排查思路先直接访问/v3/api-docs看JSON是否完整。如果JSON完整但UI白屏多半是前端渲染脚本出错可能是版本兼容问题。可以看看浏览器Console报错如果报ncaught SyntaxError: Unexpected token之类的考虑升级springdoc版本或换个浏览器试试。还有个偷懒方案是CtrlF5强制刷新去掉浏览器缓存。7.5 项目从Spring Boot 2升到3之后Swagger报错怎么办Spring Boot 3的迁移坑主因是javax包改成了jakarta。如果你升级完Boot版本发现Swagger完全扫不到接口或启动直接报错检查一下依赖坐标是不是springdoc-openapi-starter-webmvc-ui这个新版。旧版的springdoc-openapi-ui在这个环境下十有八九启动失败。另外Spring Boot 3要求JDK 17以上如果你的项目环境是JDK 8建议还是留在Spring Boot 2.x配套springdoc 1.x没必要强行升级。搜索引擎里那些springboot版本太高导致Swagger无法使用的提问基本就是版本线没对齐导致的。7.6 JApiDocs在Linux服务器上生成文档乱码这个问题很刁钻。JApiDocs读取源码文件时默认编码受系统环境变量影响。Windows本地开发环境默认GBKLinux服务器默认UTF-8如果你的源码是UTF-8但生成环境是Windows读取时可能乱码。解决方式是在DocsConfig里指定编码集config.setCharset(UTF-8);有的老版本可能没有这个setter那就用JVM参数手动指定默认字符集java -Dfile.encodingUTF-8 -jar japidocs-generator.jar我在CI流水线上遇到过这个坑最后就是用-Dfile.encodingUTF-8解决的。如果你也把文档生成放进CI记得把这一步写进流水线的Java命令参数里。8. 个人实践结论双工具不是非此即彼但要分清主次工具不是信仰是手段。Swagger和JApiDocs各有各的适用场景我自己的经验是这么分配的个人主导的小型项目、开源项目我会优先用JApiDocs。因为不想让Controller方法被一大堆注解弄得面目全非接口数量少的时候注释顺手写一写文档就出来了而且发出去的HTML文档很干净别人拿去看不需要本地环境。唯一的代价是联调时前端要在Postman里自己填参数不像Swagger UI那样点一下就帮忙把参数数据结构展示出来。团队协作的大中型项目我基本会选择Swagger。联调效率是最大的考量。前端有Swagger UI可以直接看到每个接口的示例参数可以拷贝JSON示例再调整甚至直接在UI里发起请求自测。虽然Controller代码会多出不少注解但换来的是清晰分组的在线演示界面。如果你实在难以抉择还有个折中方案主力用Swagger做联调和在线文档CI阶段用JApiDocs生成一份离线HTML归档。这样平时开发效率不受影响每次发布也有一份静态文档沉淀下来方便追溯历史版本。代价是两套工具的注释规则都要遵守代码里既有注解又有Javadoc。我在一个中等规模项目里这么干过半年效果还行但对团队代码规范要求比较高不是所有团队都撑得住。在决定引入哪个工具之前建议先拿团队的真实代码跑一个Demo看生成文档后的实际效果再做决定。不同团队不同的代码习惯直接套用别人的结论很可能水土不服。