Gretty 源码深度剖析:从 Gradle 插件到 Runner 的完整执行链路
【免费下载链接】grettyAdvanced gradle plugin for running web-apps on jetty and tomcat.项目地址: https://gitcode.com/gh_mirrors/gr/gretty
当你在build.gradle里敲下apply plugin: 'org.akhikhl.gretty',再执行一行gradle appRun,一个 Jetty 或 Tomcat 容器便神奇地启动起来,浏览器自动弹出你的 Web 应用。Gretty正是这样一个高级 Gradle 插件,它把"构建 Web 应用并在 Jetty/Tomcat 上运行"这件事简化到了极致。但这一行命令背后,到底发生了什么?从Gradle 插件注册任务、到DefaultLauncher拉起独立 JVM、再到Runner进程里启动 Servlet 容器,是一条设计精巧的完整执行链路。本文将从源码角度,把这条链路从头到尾拆给你看,帮助你真正理解 Gretty 的运行机制。
一、Gretty 源码整体架构:三个关键层次 🏗️
在深入执行链路之前,先认识 Gretty 的源码布局。克隆仓库后,核心代码集中在libs/目录下,可以分成三个层次:
| 层次 | 模块 | 职责 |
|---|---|---|
| Gradle 插件层 | libs/gretty/ | 与 Gradle 深度耦合,注册任务、扩展、配置、依赖 |
| 桥接层 | libs/gretty-core/、libs/gretty-runner/ | 进程启动、端口协商、服务协议、Runner 主类 |
| 容器适配层 | gretty-runner-jetty7/8/9/93/94、gretty-runner-tomcat/7/8 | 针对不同 Servlet 容器实现ServerManager |
这种分层设计非常巧妙:Gradle 插件层(libs/gretty)只负责与构建系统交互,容器适配层完全独立于 Gradle,甚至可以直接复用。理解了这三个层次,整条执行链路就清晰了一半。
二、执行链路总览:一次 appRun 的完整旅程 🚀
一次gradle appRun的完整执行,大致经历以下五个阶段:
- 插件入口:
GrettyPlugin.apply()注册扩展与任务 - 任务执行:
AppStartTask组装配置,构造DefaultLauncher - 进程拉起:
LauncherBase协商端口,通过javaexec启动 Runner 子进程 - 服务循环:
Runner主进程监听命令,调用ServerManager启动容器 - 交互控制:Gradle 侧监听按键,向 Runner 发送 restart/stop 指令
下面我们逐个阶段深入剖析。
三、第一阶段:插件入口 —— GrettyPlugin 干了什么 🔌
一切从GrettyPlugin开始,它是整个插件的总开关,源码位于 GrettyPlugin.groovy。它的apply()方法主要做了四件事:
1. 注册扩展(DSL)
project.extensions.create('gretty', GrettyExtension) project.extensions.create('farm', FarmExtension, project) project.extensions.create('product', ProductExtension)这就是你能在build.gradle里写gretty { ... }、farm { ... }配置块的来源。
2. 批量注册任务在addTasks()方法中,插件一口气注册了几十个任务:appRun、appStart、appStop、appRestart、jettyRun、tomcatRun,以及各自的 Debug 变体(如appRunDebug),还有集成测试配套的appBeforeIntegrationTest/appAfterIntegrationTest。
3. 建立任务依赖在addTaskDependencies()中,appRun会依赖prepareInplaceWebApp或prepareArchiveWebApp,确保启动前 Web 应用资源已准备就绪。
4. 注入依赖配置插件还会为 Spring Boot 项目自动排除内嵌 Tomcat/Jetty,避免与 Gretty 管理的容器冲突,这正是 Gretty 能优雅支持 Spring Boot 的关键细节。
💡小知识:
GrettyStartTask类已标记@Deprecated,建议直接使用AppStartTask,源码见 GrettyStartTask.groovy。
四、第二阶段:任务执行 —— StartBaseTask 的模板方法 🎯
运行appRun时,实际执行的是AppStartTask(继承自StartBaseTask)。StartBaseTask是一个经典的模板方法模式实现,源码见 StartBaseTask.groovy。
它的核心是@TaskAction action()方法:
LauncherConfig config = getLauncherConfig() Launcher launcher = new DefaultLauncher(project, config) launcher.scannerManager = createScannerManager(config, ...) launcher.launch()而AppStartTask.getStartConfig()(见 AppStartTask.groovy)负责把 DSL 配置、项目默认值、命令行参数合并成最终的ServerConfig和WebAppConfig,并调用doPrepareServerConfig()处理证书生成、Jacoco 参数注入、SpringLoaded 热加载 agent 等细节。
五、第三阶段:进程拉起 —— LauncherBase 与端口协商 🔄
DefaultLauncher继承了LauncherBase(源码在 LauncherBase.groovy),这里藏着两个重要机制:
1. 端口协商beforeLaunch()会先检查build/gretty_ports属性文件,判断服务器是否已在运行;如果未运行,则通过findFreePorts()分配两个空闲端口:servicePort(服务端口,用于接收命令)和statusPort(状态端口,用于回报启动状态),并写入属性文件。
2. 启动子进程launchThread()中会构造JavaExecParams,以org.akhikhl.gretty.Runner为主类,通过javaexec在独立的 JVM 进程中启动服务器:
params.main = 'org.akhikhl.gretty.Runner' params.args = [ "--servicePort=${servicePort}", "--statusPort=${statusPort}", "--serverManagerFactory=${getServerManagerFactory()}" ]🔑设计亮点:服务器运行在独立进程中,与 Gradle 守护进程隔离。即使 Gradle 任务结束,服务器也能存活,这也是
appStart(非交互模式)能后台常驻的原因。
六、第四阶段:Runner 主进程 —— 命令驱动的服务循环 🧠
Runner是独立 JVM 的入口(源码见 Runner.groovy),它的main()解析三个命令行参数后,进入一个死循环:
- 创建
ServerSocket监听servicePort,并向 Gradle 侧发送init信号 - 循环读取 Gradle 侧发来的 JSON 命令
- 首次收到配置后,加载 logback 日志配置,调用
ServerManager.startServer() - 之后根据命令响应:
status返回启动状态、stop停止服务器并退出、restart重启容器、redeploy xxx热部署指定 Web 应用
这个机制让"Gradle 任务已退出、服务器仍在运行"成为可能:服务器启动后,appRun的交互循环只是不停读取键盘输入,并把按键翻译成restartWithEvent等命令发给 Runner。
七、第五阶段:ServerManager —— 容器适配层的抽象 💡
ServerManager是容器适配层的核心接口(ServerManager.groovy),只定义四个方法:
setParams()—— 注入运行参数startServer()—— 启动容器stopServer()—— 停止容器redeploy()—— 热部署指定应用
每个容器版本提供自己的实现:gretty-runner-jetty9、gretty-runner-jetty94负责 Jetty 各版本,gretty-runner-tomcat8等负责 Tomcat。Runner 通过Class.forName(params.serverManagerFactory)反射加载对应的工厂类,从而与具体容器解耦。
这种"一个 Runner + 多个 ServerManager 实现"的架构,让 Gretty 只需一份命令协议,就能无缝支持 Jetty 7/8/9/9.3/9.4 与 Tomcat 7/8 全家桶。
八、热部署与自动重启的秘密:Scanner 机制 🔍
执行链路之外,Gretty 最受好评的热部署功能也值得一看。StartBaseTask的createScannerManager()支持两种扫描器(源码见 scanner/JDKScannerManager.groovy 和 JettyScannerManager.groovy):
- JDK 扫描器:基于
WatchService,性能更高,优先使用 - Jetty 扫描器:作为 JDK 扫描器不可用时的降级方案
当扫描器检测到 class 或资源变化时,会通过asyncResponse向 Runner 发送restartWithEvent命令并等待完成事件,实现优雅的热重启。若开启了 Spring Loaded 的managedClassReload,还能实现类级别的热替换。
九、进阶场景:Farm 多应用集群与集成测试 🧪
如果你有多个 Web 应用需要同时运行,Gretty 的Farm机制会派出用场。GrettyPlugin.addTasks()会为每个 farm 生成farmRun、farmStart、farmStop等一系列任务,在同一个容器中部署多个应用。
此外,appBeforeIntegrationTest/appAfterIntegrationTest任务与integrationTest任务配合,可以在测试前自动启动服务器、测试后自动关闭——配合integrationTests/目录下的大量示例(如 farm/、helloGretty/),你可以快速学会各种场景的用法。
十、总结:一次点击背后的精妙设计 🎉
回看整条链路:Gradle 插件注册任务 → 任务组装配置 → Launcher 协商端口 → 独立 JVM 中的 Runner 循环 → ServerManager 启动容器 → Scanner 驱动热部署。Gretty 的成功源于两个关键决策:
- 进程隔离:服务器跑在独立 JVM 中,与 Gradle 生命周期解耦
- 协议抽象:统一的服务协议 + 可插拔的容器适配层,一份命令通吃所有容器
希望这篇 Gretty 源码剖析能帮你建立起从 Gradle 插件到 Runner 的完整认知。下次运行gradle appRun时,你就能清楚地知道,屏幕背后那条精心设计的执行链路正在如何为你工作。如果想亲手验证,可以用git clone https://gitcode.com/gh_mirrors/gr/gretty拉取源码,边读边调试,收获会更大。
【免费下载链接】grettyAdvanced gradle plugin for running web-apps on jetty and tomcat.项目地址: https://gitcode.com/gh_mirrors/gr/gretty
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考